credential/userpass: configuring users

This commit is contained in:
Mitchell Hashimoto
2015-04-19 14:59:30 -07:00
parent 3eaffc70e8
commit c62d30760c
3 changed files with 239 additions and 0 deletions

View 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".
`

View 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
},
}
}

View 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.
`