vault: wire tokens into expiration manager

This commit is contained in:
Armon Dadgar 2015-03-23 18:11:15 -07:00
parent 32ef2c4a32
commit 3ccd20cb58
3 changed files with 128 additions and 10 deletions

View file

@ -282,18 +282,10 @@ func (c *Core) HandleLogin(req *credential.Request) (*credential.Response, error
// Register with the expiration manager if there is a lease
if resp.Secret.Lease > 0 {
lReq := &logical.Request{
Path: req.Path,
Data: req.Data,
}
lResp := &logical.Response{
Secret: resp.Secret,
Data: resp.Data,
}
vaultID, err := c.expiration.Register(lReq, lResp)
vaultID, err := c.expiration.RegisterLogin(te.ID, req, resp)
if err != nil {
c.logger.Printf(
"[ERR] core: failed to register lease "+
"[ERR] core: failed to register login token lease "+
"(request: %#v, response: %#v): %v", req, resp, err)
return nil, ErrInternalError
}

View file

@ -10,6 +10,7 @@ import (
"sync"
"time"
"github.com/hashicorp/vault/credential"
"github.com/hashicorp/vault/logical"
)
@ -300,6 +301,50 @@ func (m *ExpirationManager) Register(req *logical.Request, resp *logical.Respons
return le.VaultID, nil
}
// RegisterLogin is used to take a credential request and response with
// an associated lease. The secret gets assigned a vaultId and the management of
// of lease is assumed by the expiration manager. This is distinct from Register
// as the behavior of renew and revocation differs a bit.
func (m *ExpirationManager) RegisterLogin(token string, req *credential.Request, resp *credential.Response) (string, error) {
// Ignore if there is no leased secret
if resp == nil || resp.Secret == nil || resp.Secret.Lease == 0 {
return "", nil
}
// Validate the secret
if err := resp.Secret.Validate(); err != nil {
return "", err
}
// Create a lease entry
now := time.Now().UTC()
leaseTotal := resp.Secret.Lease + resp.Secret.LeaseGracePeriod
le := leaseEntry{
VaultID: path.Join(req.Path, generateUUID()),
LoginToken: token,
Path: req.Path,
Data: resp.Data,
Secret: resp.Secret,
IssueTime: now,
ExpireTime: now.Add(leaseTotal),
}
// Encode the entry
if err := m.persistEntry(&le); err != nil {
return "", err
}
// Setup revocation timer
m.pendingLock.Lock()
m.pending[le.VaultID] = time.AfterFunc(leaseTotal, func() {
m.expireID(le.VaultID)
})
m.pendingLock.Unlock()
// Done
return le.VaultID, nil
}
// expireID is invoked when a given ID is expired
func (m *ExpirationManager) expireID(vaultID string) {
// Clear from the pending expiration
@ -321,6 +366,16 @@ func (m *ExpirationManager) expireID(vaultID string) {
// revokeEntry is used to attempt revocation of an internal entry
func (m *ExpirationManager) revokeEntry(le *leaseEntry) error {
// Revocation of login tokens is special since we can by-pass the
// backend and directly interact with the token store
if le.LoginToken != "" {
if err := m.tokenStore.RevokeTree(le.LoginToken); err != nil {
return fmt.Errorf("failed to revoke token: %v", err)
}
return nil
}
// Handle standard revocation via backends
_, err := m.router.Route(logical.RevokeRequest(
le.Path, le.Secret, le.Data))
if err != nil {
@ -390,6 +445,7 @@ func (m *ExpirationManager) deleteEntry(vaultID string) error {
// manager stores. This is used to handle renew and revocation.
type leaseEntry struct {
VaultID string `json:"vault_id"`
LoginToken string `json:"login_token"`
Path string `json:"path"`
Data map[string]interface{} `json:"data"`
Secret *logical.Secret `json:"secret"`

View file

@ -9,6 +9,7 @@ import (
"testing"
"time"
"github.com/hashicorp/vault/credential"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical"
)
@ -126,6 +127,40 @@ func TestExpiration_Register(t *testing.T) {
}
}
func TestExpiration_RegisterLogin(t *testing.T) {
exp := mockExpiration(t)
root, err := exp.tokenStore.RootToken()
if err != nil {
t.Fatalf("err: %v", err)
}
req := &credential.Request{
Path: "auth/user/login",
}
resp := &credential.Response{
Secret: &logical.Secret{
Lease: time.Hour,
},
Data: map[string]interface{}{
"access_key": "xyz",
"secret_key": "abcd",
},
}
id, err := exp.RegisterLogin(root.ID, req, resp)
if err != nil {
t.Fatalf("err: %v", err)
}
if !strings.HasPrefix(id, req.Path) {
t.Fatalf("bad: %s", id)
}
if len(id) <= len(req.Path) {
t.Fatalf("bad: %s", id)
}
}
func TestExpiration_Revoke(t *testing.T) {
exp := mockExpiration(t)
noop := &NoopBackend{}
@ -407,6 +442,41 @@ func TestExpiration_revokeEntry(t *testing.T) {
}
}
func TestExpiration_revokeEntry_token(t *testing.T) {
exp := mockExpiration(t)
root, err := exp.tokenStore.RootToken()
if err != nil {
t.Fatalf("err: %v", err)
}
le := &leaseEntry{
VaultID: "foo/bar/1234",
LoginToken: root.ID,
Path: "foo/bar",
Data: map[string]interface{}{
"testing": true,
},
Secret: &logical.Secret{
Lease: time.Minute,
},
IssueTime: time.Now(),
ExpireTime: time.Now(),
}
err = exp.revokeEntry(le)
if err != nil {
t.Fatalf("err: %v", err)
}
out, err := exp.tokenStore.Lookup(root.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("bad: %v", out)
}
}
func TestExpiration_renewEntry(t *testing.T) {
exp := mockExpiration(t)