mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-28 22:21:30 +01:00
credential/userpass: configuring users
This commit is contained in:
parent
3eaffc70e8
commit
c62d30760c
47
builtin/credential/userpass/backend.go
Normal file
47
builtin/credential/userpass/backend.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package userpass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/vault/logical"
|
||||||
|
"github.com/hashicorp/vault/logical/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Factory(map[string]string) (logical.Backend, error) {
|
||||||
|
return Backend(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Backend() *framework.Backend {
|
||||||
|
var b backend
|
||||||
|
b.Backend = &framework.Backend{
|
||||||
|
Help: backendHelp,
|
||||||
|
|
||||||
|
PathsSpecial: &logical.Paths{
|
||||||
|
Root: []string{
|
||||||
|
"users/*",
|
||||||
|
},
|
||||||
|
|
||||||
|
Unauthenticated: []string{
|
||||||
|
"login",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Paths: append([]*framework.Path{
|
||||||
|
pathUsers(&b),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Backend
|
||||||
|
}
|
||||||
|
|
||||||
|
type backend struct {
|
||||||
|
*framework.Backend
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendHelp = `
|
||||||
|
The "userpass" credential provider allows authentication using
|
||||||
|
a combination of a username and password. No additional factors
|
||||||
|
are supported.
|
||||||
|
|
||||||
|
The username/password combination is configured using the "users/"
|
||||||
|
endpoints by a user with root access. Authentication is then done
|
||||||
|
by suppying the two fields for "login".
|
||||||
|
`
|
||||||
72
builtin/credential/userpass/backend_test.go
Normal file
72
builtin/credential/userpass/backend_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package userpass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/logical"
|
||||||
|
logicaltest "github.com/hashicorp/vault/logical/testing"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBackend_userCrud(t *testing.T) {
|
||||||
|
b := Backend()
|
||||||
|
|
||||||
|
logicaltest.Test(t, logicaltest.TestCase{
|
||||||
|
Backend: b,
|
||||||
|
Steps: []logicaltest.TestStep{
|
||||||
|
testAccStepUser(t, "web", "password", "foo"),
|
||||||
|
testAccStepReadUser(t, "web", "foo"),
|
||||||
|
testAccStepDeleteUser(t, "web"),
|
||||||
|
testAccStepReadUser(t, "web", ""),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccStepUser(
|
||||||
|
t *testing.T, name string, password string, policies string) logicaltest.TestStep {
|
||||||
|
return logicaltest.TestStep{
|
||||||
|
Operation: logical.WriteOperation,
|
||||||
|
Path: "users/" + name,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"password": password,
|
||||||
|
"policies": policies,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccStepDeleteUser(t *testing.T, n string) logicaltest.TestStep {
|
||||||
|
return logicaltest.TestStep{
|
||||||
|
Operation: logical.DeleteOperation,
|
||||||
|
Path: "users/" + n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccStepReadUser(t *testing.T, name string, policies string) logicaltest.TestStep {
|
||||||
|
return logicaltest.TestStep{
|
||||||
|
Operation: logical.ReadOperation,
|
||||||
|
Path: "users/" + name,
|
||||||
|
Check: func(resp *logical.Response) error {
|
||||||
|
if resp == nil {
|
||||||
|
if policies == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("bad: %#v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var d struct {
|
||||||
|
Policies string `mapstructure:"policies"`
|
||||||
|
}
|
||||||
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Policies != policies {
|
||||||
|
return fmt.Errorf("bad: %#v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
120
builtin/credential/userpass/path_users.go
Normal file
120
builtin/credential/userpass/path_users.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package userpass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/logical"
|
||||||
|
"github.com/hashicorp/vault/logical/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pathUsers(b *backend) *framework.Path {
|
||||||
|
return &framework.Path{
|
||||||
|
Pattern: `users/(?P<name>\w+)`,
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"name": &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.TypeString,
|
||||||
|
Description: "Comma-separated list of policies",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
logical.DeleteOperation: b.pathUserDelete,
|
||||||
|
logical.ReadOperation: b.pathUserRead,
|
||||||
|
logical.WriteOperation: b.pathUserWrite,
|
||||||
|
},
|
||||||
|
|
||||||
|
HelpSynopsis: pathUserHelpSyn,
|
||||||
|
HelpDescription: pathUserHelpDesc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) User(s logical.Storage, n string) (*UserEntry, error) {
|
||||||
|
entry, err := s.Get("user/" + n)
|
||||||
|
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) pathUserDelete(
|
||||||
|
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
err := req.Storage.Delete("user/" + d.Get("name").(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathUserRead(
|
||||||
|
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
user, err := b.User(req.Storage, d.Get("name").(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logical.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"policies": strings.Join(user.Policies, ","),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backend) pathUserWrite(
|
||||||
|
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
policies := strings.Split(d.Get("policies").(string), ",")
|
||||||
|
for i, p := range policies {
|
||||||
|
policies[i] = strings.TrimSpace(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store it
|
||||||
|
entry, err := logical.StorageEntryJSON("user/"+name, &UserEntry{
|
||||||
|
Password: d.Get("password").(string),
|
||||||
|
Policies: policies,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := req.Storage.Put(entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserEntry struct {
|
||||||
|
Password string
|
||||||
|
Policies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
`
|
||||||
Loading…
x
Reference in New Issue
Block a user