vault: Adding auth/token/create endpoint
This commit is contained in:
parent
b5332404d1
commit
6fd3cae2c2
|
@ -30,7 +30,7 @@ func mockExpiration(t *testing.T) *ExpirationManager {
|
|||
// Create the barrier view
|
||||
view := NewBarrierView(b, "expire/")
|
||||
|
||||
_, ts := mockTokenStore(t)
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
|
||||
router := NewRouter()
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/credential"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
|
@ -294,28 +296,155 @@ func (ts *TokenStore) RevokeAll() error {
|
|||
|
||||
// HandleRequest is used to handle a request and generate a response.
|
||||
// The backends must check the operation type and handle appropriately.
|
||||
func (ts *TokenStore) HandleRequest(*logical.Request) (*logical.Response, error) {
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
func (ts *TokenStore) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
||||
switch {
|
||||
case req.Path == "create":
|
||||
return ts.handleCreate(req)
|
||||
case strings.HasPrefix(req.Path, "revoke-orphan/"):
|
||||
return ts.handleRevokeOrphan(req)
|
||||
case req.Path == "":
|
||||
switch req.Operation {
|
||||
case logical.HelpOperation:
|
||||
return logical.HelpResponse(tokenBackendHelp, []string{"auth/token/create"}), nil
|
||||
default:
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
return nil, logical.ErrUnsupportedPath
|
||||
}
|
||||
|
||||
// RootPaths is a list of paths that require root level privileges.
|
||||
// These paths will be enforced by the router so that backends do
|
||||
// not need to handle the authorization. Paths are enforced exactly
|
||||
// or using a prefix match if they end in '*'
|
||||
func (ts *TokenStore) RootPaths() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoginPaths is a list of paths that are unauthenticated and used
|
||||
// only for logging in. These paths cannot be reached via HandleRequest,
|
||||
// and are sent to HandleLogin instead. Paths are enforced exactly
|
||||
// or using a prefix match if they end in '*'
|
||||
func (ts *TokenStore) LoginPaths() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleLogin is used to handle a login request and generate a response.
|
||||
// The backend is allowed to ignore this request if it is not applicable.
|
||||
func (ts *TokenStore) HandleLogin(req *credential.Request) (*credential.Response, error) {
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
|
||||
// handleCreate handles the auth/token/create path for creation of new tokens
|
||||
func (ts *TokenStore) handleCreate(req *logical.Request) (*logical.Response, error) {
|
||||
// Validate the operation
|
||||
switch req.Operation {
|
||||
case logical.WriteOperation:
|
||||
case logical.HelpOperation:
|
||||
return logical.HelpResponse(tokenCreateHelp, nil), nil
|
||||
default:
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
|
||||
// Read the parent policy
|
||||
parent, err := ts.Lookup(req.ClientToken)
|
||||
if err != nil || parent == nil {
|
||||
return logical.ErrorResponse("parent token lookup failed"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Check if the parent policy is root
|
||||
isRoot := strListContains(parent.Policies, "root")
|
||||
|
||||
// Read and parse the fields
|
||||
idRaw, _ := req.Data["id"]
|
||||
policiesRaw, _ := req.Data["policies"]
|
||||
metaRaw, _ := req.Data["meta"]
|
||||
noParentRaw, _ := req.Data["no_parent"]
|
||||
leaseRaw, _ := req.Data["lease"]
|
||||
|
||||
// Setup the token entry
|
||||
te := TokenEntry{
|
||||
Parent: req.ClientToken,
|
||||
Path: "auth/token/create",
|
||||
}
|
||||
|
||||
// Allow specifying the ID of the token if the client is root
|
||||
if id, ok := idRaw.(string); ok {
|
||||
if !isRoot {
|
||||
return logical.ErrorResponse("root required to specify token id"),
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
te.ID = id
|
||||
}
|
||||
|
||||
// Only permit policies to be a subset unless the client is root
|
||||
if policies, ok := policiesRaw.([]string); ok {
|
||||
if !isRoot && !strListSubset(parent.Policies, policies) {
|
||||
return logical.ErrorResponse("child policies must be subset of parent"), logical.ErrInvalidRequest
|
||||
}
|
||||
te.Policies = policies
|
||||
}
|
||||
|
||||
// Ensure is some associated policy
|
||||
if len(te.Policies) == 0 {
|
||||
return logical.ErrorResponse("token must have at least one policy"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Only allow an orphan token if the client is root
|
||||
if noParent, _ := noParentRaw.(bool); noParent {
|
||||
if !isRoot {
|
||||
return logical.ErrorResponse("root required to create orphan token"),
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
te.Parent = ""
|
||||
}
|
||||
|
||||
// Parse any metadata associated with the token
|
||||
if meta, ok := metaRaw.(map[string]interface{}); ok {
|
||||
te.Meta = meta
|
||||
}
|
||||
|
||||
// Parse the lease if any
|
||||
var secret *logical.Secret
|
||||
if lease, ok := leaseRaw.(string); ok {
|
||||
dur, err := time.ParseDuration(lease)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
||||
}
|
||||
if dur < 0 {
|
||||
return logical.ErrorResponse("lease must be positive"), logical.ErrInvalidRequest
|
||||
}
|
||||
secret = &logical.Secret{
|
||||
Lease: dur,
|
||||
LeaseGracePeriod: dur / 10, // Provide a 10% grace buffer
|
||||
Renewable: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Create the token
|
||||
if err := ts.Create(&te); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
Secret: secret,
|
||||
Data: map[string]interface{}{
|
||||
clientTokenKey: te.ID,
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// handleRevokeOrphan handles the auth/token/revoke-orphan/id path for revocation of tokens
|
||||
// in a way that leaves child tokens orphaned. Normally, using sys/revoke/vaultID will revoke
|
||||
// the token and all children.
|
||||
func (ts *TokenStore) handleRevokeOrphan(req *logical.Request) (*logical.Response, error) {
|
||||
// Validate the operation
|
||||
switch req.Operation {
|
||||
case logical.WriteOperation:
|
||||
default:
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const (
|
||||
tokenBackendHelp = `The token credential backend is always enabled and builtin to Vault.
|
||||
Client tokens are used to identify a client and to allow Vault to associate policies and ACLs
|
||||
which are enforced on every request. This backend also allows for generating sub-tokens as well
|
||||
as revocation of tokens.`
|
||||
|
||||
tokenCreateHelp = `The token create path is used to create new tokens.`
|
||||
)
|
||||
|
|
|
@ -3,19 +3,22 @@ package vault
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func mockTokenStore(t *testing.T) (*Core, *TokenStore) {
|
||||
c, _ := TestCoreUnsealed(t)
|
||||
func mockTokenStore(t *testing.T) (*Core, *TokenStore, string) {
|
||||
c, _, root := TestCoreUnsealedToken(t)
|
||||
ts, err := NewTokenStore(c)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
return c, ts
|
||||
return c, ts, root
|
||||
}
|
||||
|
||||
func TestTokenStore_RootToken(t *testing.T) {
|
||||
_, ts := mockTokenStore(t)
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
|
||||
te, err := ts.RootToken()
|
||||
if err != nil {
|
||||
|
@ -35,7 +38,7 @@ func TestTokenStore_RootToken(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_CreateLookup(t *testing.T) {
|
||||
c, ts := mockTokenStore(t)
|
||||
c, ts, _ := mockTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
|
||||
if err := ts.Create(ent); err != nil {
|
||||
|
@ -70,7 +73,7 @@ func TestTokenStore_CreateLookup(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_CreateLookup_ProvidedID(t *testing.T) {
|
||||
c, ts := mockTokenStore(t)
|
||||
c, ts, _ := mockTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{
|
||||
ID: "foobarbaz",
|
||||
|
@ -109,7 +112,7 @@ func TestTokenStore_CreateLookup_ProvidedID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_Revoke(t *testing.T) {
|
||||
_, ts := mockTokenStore(t)
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
|
||||
if err := ts.Create(ent); err != nil {
|
||||
|
@ -135,7 +138,7 @@ func TestTokenStore_Revoke(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_Revoke_Orphan(t *testing.T) {
|
||||
_, ts := mockTokenStore(t)
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
|
||||
ent := &TokenEntry{Path: "test", Policies: []string{"dev", "ops"}}
|
||||
if err := ts.Create(ent); err != nil {
|
||||
|
@ -162,7 +165,7 @@ func TestTokenStore_Revoke_Orphan(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_RevokeTree(t *testing.T) {
|
||||
_, ts := mockTokenStore(t)
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
|
||||
ent1 := &TokenEntry{}
|
||||
if err := ts.Create(ent1); err != nil {
|
||||
|
@ -206,7 +209,7 @@ func TestTokenStore_RevokeTree(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenStore_RevokeAll(t *testing.T) {
|
||||
_, ts := mockTokenStore(t)
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
|
||||
ent1 := &TokenEntry{}
|
||||
if err := ts.Create(ent1); err != nil {
|
||||
|
@ -244,3 +247,222 @@ func TestTokenStore_RevokeAll(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NoPolicy(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = root
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data["error"] != "token must have at least one policy" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_BadParent(t *testing.T) {
|
||||
_, ts, _ := mockTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = "random"
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data["error"] != "parent token lookup failed" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = root
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data[clientTokenKey] == "" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_RootID(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = root
|
||||
req.Data["id"] = "foobar"
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data[clientTokenKey] != "foobar" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", []string{"foo"})
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = "client"
|
||||
req.Data["id"] = "foobar"
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data["error"] != "root required to specify token id" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRoot_Subset(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", []string{"foo", "bar"})
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = "client"
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data[clientTokenKey] == "" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRoot_InvalidSubset(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", []string{"foo", "bar"})
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = "client"
|
||||
req.Data["policies"] = []string{"foo", "bar", "baz"}
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data["error"] == "child policies must be subset of parent" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_NonRoot_NoParent(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
testMakeToken(t, ts, root, "client", []string{"foo"})
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = "client"
|
||||
req.Data["no_parent"] = true
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data["error"] != "root required to create orphan token" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_Root_NoParent(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = root
|
||||
req.Data["no_parent"] = true
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data[clientTokenKey] == "" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
|
||||
out, _ := ts.Lookup(resp.Data[clientTokenKey].(string))
|
||||
if out.Parent != "" {
|
||||
t.Fatalf("bad: %#v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_Metadata(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = root
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
meta := map[string]interface{}{
|
||||
"user": "armon",
|
||||
"source": "github",
|
||||
}
|
||||
req.Data["meta"] = meta
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data[clientTokenKey] == "" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
|
||||
out, _ := ts.Lookup(resp.Data[clientTokenKey].(string))
|
||||
if !reflect.DeepEqual(out.Meta, meta) {
|
||||
t.Fatalf("bad: %#v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_HandleRequest_CreateToken_Lease(t *testing.T) {
|
||||
_, ts, root := mockTokenStore(t)
|
||||
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = root
|
||||
req.Data["policies"] = []string{"foo"}
|
||||
req.Data["lease"] = "1h"
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data[clientTokenKey] == "" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
if resp.Secret.Lease != time.Hour {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
if !resp.Secret.Renewable {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func testMakeToken(t *testing.T, ts *TokenStore, root, client string, policy []string) {
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "create")
|
||||
req.ClientToken = root
|
||||
req.Data["id"] = client
|
||||
req.Data["policies"] = policy
|
||||
|
||||
resp, err := ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
if resp.Data[clientTokenKey] != "client" {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue