1package userpass 2 3import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 sockaddr "github.com/hashicorp/go-sockaddr" 10 "github.com/hashicorp/vault/sdk/framework" 11 "github.com/hashicorp/vault/sdk/helper/tokenutil" 12 "github.com/hashicorp/vault/sdk/logical" 13) 14 15func pathUsersList(b *backend) *framework.Path { 16 return &framework.Path{ 17 Pattern: "users/?", 18 19 Callbacks: map[logical.Operation]framework.OperationFunc{ 20 logical.ListOperation: b.pathUserList, 21 }, 22 23 HelpSynopsis: pathUserHelpSyn, 24 HelpDescription: pathUserHelpDesc, 25 DisplayAttrs: &framework.DisplayAttributes{ 26 Navigation: true, 27 ItemType: "User", 28 }, 29 } 30} 31 32func pathUsers(b *backend) *framework.Path { 33 p := &framework.Path{ 34 Pattern: "users/" + framework.GenericNameRegex("username"), 35 Fields: map[string]*framework.FieldSchema{ 36 "username": { 37 Type: framework.TypeString, 38 Description: "Username for this user.", 39 }, 40 41 "password": { 42 Type: framework.TypeString, 43 Description: "Password for this user.", 44 DisplayAttrs: &framework.DisplayAttributes{ 45 Sensitive: true, 46 }, 47 }, 48 49 "policies": { 50 Type: framework.TypeCommaStringSlice, 51 Description: tokenutil.DeprecationText("token_policies"), 52 Deprecated: true, 53 }, 54 55 "ttl": { 56 Type: framework.TypeDurationSecond, 57 Description: tokenutil.DeprecationText("token_ttl"), 58 Deprecated: true, 59 }, 60 61 "max_ttl": { 62 Type: framework.TypeDurationSecond, 63 Description: tokenutil.DeprecationText("token_max_ttl"), 64 Deprecated: true, 65 }, 66 67 "bound_cidrs": { 68 Type: framework.TypeCommaStringSlice, 69 Description: tokenutil.DeprecationText("token_bound_cidrs"), 70 Deprecated: true, 71 }, 72 }, 73 74 Callbacks: map[logical.Operation]framework.OperationFunc{ 75 logical.DeleteOperation: b.pathUserDelete, 76 logical.ReadOperation: b.pathUserRead, 77 logical.UpdateOperation: b.pathUserWrite, 78 logical.CreateOperation: b.pathUserWrite, 79 }, 80 81 ExistenceCheck: b.userExistenceCheck, 82 83 HelpSynopsis: pathUserHelpSyn, 84 HelpDescription: pathUserHelpDesc, 85 DisplayAttrs: &framework.DisplayAttributes{ 86 Action: "Create", 87 ItemType: "User", 88 }, 89 } 90 91 tokenutil.AddTokenFields(p.Fields) 92 return p 93} 94 95func (b *backend) userExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) { 96 userEntry, err := b.user(ctx, req.Storage, d.Get("username").(string)) 97 if err != nil { 98 return false, err 99 } 100 101 return userEntry != nil, nil 102} 103 104func (b *backend) user(ctx context.Context, s logical.Storage, username string) (*UserEntry, error) { 105 if username == "" { 106 return nil, fmt.Errorf("missing username") 107 } 108 109 entry, err := s.Get(ctx, "user/"+strings.ToLower(username)) 110 if err != nil { 111 return nil, err 112 } 113 if entry == nil { 114 return nil, nil 115 } 116 117 var result UserEntry 118 if err := entry.DecodeJSON(&result); err != nil { 119 return nil, err 120 } 121 122 if result.TokenTTL == 0 && result.TTL > 0 { 123 result.TokenTTL = result.TTL 124 } 125 if result.TokenMaxTTL == 0 && result.MaxTTL > 0 { 126 result.TokenMaxTTL = result.MaxTTL 127 } 128 if len(result.TokenPolicies) == 0 && len(result.Policies) > 0 { 129 result.TokenPolicies = result.Policies 130 } 131 if len(result.TokenBoundCIDRs) == 0 && len(result.BoundCIDRs) > 0 { 132 result.TokenBoundCIDRs = result.BoundCIDRs 133 } 134 135 return &result, nil 136} 137 138func (b *backend) setUser(ctx context.Context, s logical.Storage, username string, userEntry *UserEntry) error { 139 entry, err := logical.StorageEntryJSON("user/"+username, userEntry) 140 if err != nil { 141 return err 142 } 143 144 return s.Put(ctx, entry) 145} 146 147func (b *backend) pathUserList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 148 users, err := req.Storage.List(ctx, "user/") 149 if err != nil { 150 return nil, err 151 } 152 return logical.ListResponse(users), nil 153} 154 155func (b *backend) pathUserDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 156 err := req.Storage.Delete(ctx, "user/"+strings.ToLower(d.Get("username").(string))) 157 if err != nil { 158 return nil, err 159 } 160 161 return nil, nil 162} 163 164func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 165 user, err := b.user(ctx, req.Storage, strings.ToLower(d.Get("username").(string))) 166 if err != nil { 167 return nil, err 168 } 169 if user == nil { 170 return nil, nil 171 } 172 173 data := map[string]interface{}{} 174 user.PopulateTokenData(data) 175 176 // Add backwards compat data 177 if user.TTL > 0 { 178 data["ttl"] = int64(user.TTL.Seconds()) 179 } 180 if user.MaxTTL > 0 { 181 data["max_ttl"] = int64(user.MaxTTL.Seconds()) 182 } 183 if len(user.Policies) > 0 { 184 data["policies"] = data["token_policies"] 185 } 186 if len(user.BoundCIDRs) > 0 { 187 data["bound_cidrs"] = user.BoundCIDRs 188 } 189 190 return &logical.Response{ 191 Data: data, 192 }, nil 193} 194 195func (b *backend) userCreateUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 196 username := strings.ToLower(d.Get("username").(string)) 197 userEntry, err := b.user(ctx, req.Storage, username) 198 if err != nil { 199 return nil, err 200 } 201 // Due to existence check, user will only be nil if it's a create operation 202 if userEntry == nil { 203 userEntry = &UserEntry{} 204 } 205 206 if err := userEntry.ParseTokenFields(req, d); err != nil { 207 return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest 208 } 209 210 if _, ok := d.GetOk("password"); ok { 211 userErr, intErr := b.updateUserPassword(req, d, userEntry) 212 if intErr != nil { 213 return nil, intErr 214 } 215 if userErr != nil { 216 return logical.ErrorResponse(userErr.Error()), logical.ErrInvalidRequest 217 } 218 } 219 220 // handle upgrade cases 221 { 222 if err := tokenutil.UpgradeValue(d, "policies", "token_policies", &userEntry.Policies, &userEntry.TokenPolicies); err != nil { 223 return logical.ErrorResponse(err.Error()), nil 224 } 225 226 if err := tokenutil.UpgradeValue(d, "ttl", "token_ttl", &userEntry.TTL, &userEntry.TokenTTL); err != nil { 227 return logical.ErrorResponse(err.Error()), nil 228 } 229 230 if err := tokenutil.UpgradeValue(d, "max_ttl", "token_max_ttl", &userEntry.MaxTTL, &userEntry.TokenMaxTTL); err != nil { 231 return logical.ErrorResponse(err.Error()), nil 232 } 233 234 if err := tokenutil.UpgradeValue(d, "bound_cidrs", "token_bound_cidrs", &userEntry.BoundCIDRs, &userEntry.TokenBoundCIDRs); err != nil { 235 return logical.ErrorResponse(err.Error()), nil 236 } 237 } 238 239 return nil, b.setUser(ctx, req.Storage, username, userEntry) 240} 241 242func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 243 password := d.Get("password").(string) 244 if req.Operation == logical.CreateOperation && password == "" { 245 return logical.ErrorResponse("missing password"), logical.ErrInvalidRequest 246 } 247 return b.userCreateUpdate(ctx, req, d) 248} 249 250type UserEntry struct { 251 tokenutil.TokenParams 252 253 // Password is deprecated in Vault 0.2 in favor of 254 // PasswordHash, but is retained for backwards compatibility. 255 Password string 256 257 // PasswordHash is a bcrypt hash of the password. This is 258 // used instead of the actual password in Vault 0.2+. 259 PasswordHash []byte 260 261 Policies []string 262 263 // Duration after which the user will be revoked unless renewed 264 TTL time.Duration 265 266 // Maximum duration for which user can be valid 267 MaxTTL time.Duration 268 269 BoundCIDRs []*sockaddr.SockAddrMarshaler 270} 271 272const pathUserHelpSyn = ` 273Manage users allowed to authenticate. 274` 275 276const pathUserHelpDesc = ` 277This endpoint allows you to create, read, update, and delete users 278that are allowed to authenticate. 279 280Deleting a user will not revoke auth for prior authenticated users 281with that name. To do this, do a revoke on "login/<username>" for 282the username you want revoked. If you don't need to revoke login immediately, 283then the next renew will cause the lease to expire. 284` 285