From a44eb0dcd056d749fce3fe84ece4d17a227ac0d3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Apr 2015 17:21:31 -0700 Subject: [PATCH] http: renew endpoints --- http/handler.go | 1 + http/logical.go | 93 +++++++++++++++++++----------------- http/sys_lease.go | 48 +++++++++++++++++++ http/sys_lease_test.go | 33 +++++++++++++ vault/logical_passthrough.go | 2 +- 5 files changed, 132 insertions(+), 45 deletions(-) diff --git a/http/handler.go b/http/handler.go index 09f88986d..212c7affb 100644 --- a/http/handler.go +++ b/http/handler.go @@ -26,6 +26,7 @@ func Handler(core *vault.Core) http.Handler { mux.Handle("/v1/sys/remount", handleSysRemount(core)) mux.Handle("/v1/sys/policy", handleSysListPolicies(core)) mux.Handle("/v1/sys/policy/", handleSysPolicy(core)) + mux.Handle("/v1/sys/renew/", handleSysRenew(core)) mux.Handle("/v1/sys/revoke/", handleSysRevoke(core)) mux.Handle("/v1/sys/revoke-prefix/", handleSysRevokePrefix(core)) mux.Handle("/v1/sys/auth/", handleSysAuth(core)) diff --git a/http/logical.go b/http/logical.go index 2df4aa325..9a6946fcb 100644 --- a/http/logical.go +++ b/http/logical.go @@ -73,53 +73,58 @@ func handleLogical(core *vault.Core) http.Handler { return } - var httpResp interface{} - if resp != nil { - if resp.Redirect != "" { - // If we have a redirect, redirect! We use a 302 code - // because we don't actually know if its permanent. - http.Redirect(w, r, resp.Redirect, 302) - return - } + // Build the proper response + respondLogical(w, r, resp) + }) +} - logicalResp := &LogicalResponse{Data: resp.Data} - if resp.Secret != nil { - logicalResp.LeaseID = resp.Secret.LeaseID - logicalResp.Renewable = resp.Secret.Renewable - logicalResp.LeaseDuration = int(resp.Secret.Lease.Seconds()) - } - - // If we have authentication information, then set the cookie - // and setup the result structure. - if resp.Auth != nil { - expireDuration := 365 * 24 * time.Hour - if logicalResp.LeaseDuration != 0 { - expireDuration = - time.Duration(logicalResp.LeaseDuration) * time.Second - } - - http.SetCookie(w, &http.Cookie{ - Name: AuthCookieName, - Value: resp.Auth.ClientToken, - Path: "/", - Expires: time.Now().UTC().Add(expireDuration), - }) - - logicalResp.Auth = &Auth{ - ClientToken: resp.Auth.ClientToken, - Policies: resp.Auth.Policies, - Metadata: resp.Auth.Metadata, - LeaseDuration: int(resp.Auth.Lease.Seconds()), - Renewable: resp.Auth.Renewable, - } - } - - httpResp = logicalResp +func respondLogical(w http.ResponseWriter, r *http.Request, resp *logical.Response) { + var httpResp interface{} + if resp != nil { + if resp.Redirect != "" { + // If we have a redirect, redirect! We use a 302 code + // because we don't actually know if its permanent. + http.Redirect(w, r, resp.Redirect, 302) + return } - // Respond - respondOk(w, httpResp) - }) + logicalResp := &LogicalResponse{Data: resp.Data} + if resp.Secret != nil { + logicalResp.LeaseID = resp.Secret.LeaseID + logicalResp.Renewable = resp.Secret.Renewable + logicalResp.LeaseDuration = int(resp.Secret.Lease.Seconds()) + } + + // If we have authentication information, then set the cookie + // and setup the result structure. + if resp.Auth != nil { + expireDuration := 365 * 24 * time.Hour + if logicalResp.LeaseDuration != 0 { + expireDuration = + time.Duration(logicalResp.LeaseDuration) * time.Second + } + + http.SetCookie(w, &http.Cookie{ + Name: AuthCookieName, + Value: resp.Auth.ClientToken, + Path: "/", + Expires: time.Now().UTC().Add(expireDuration), + }) + + logicalResp.Auth = &Auth{ + ClientToken: resp.Auth.ClientToken, + Policies: resp.Auth.Policies, + Metadata: resp.Auth.Metadata, + LeaseDuration: int(resp.Auth.Lease.Seconds()), + Renewable: resp.Auth.Renewable, + } + } + + httpResp = logicalResp + } + + // Respond + respondOk(w, httpResp) } type LogicalResponse struct { diff --git a/http/sys_lease.go b/http/sys_lease.go index d1c68e533..4e1e1c22a 100644 --- a/http/sys_lease.go +++ b/http/sys_lease.go @@ -1,6 +1,7 @@ package http import ( + "io" "net/http" "strings" @@ -8,6 +9,49 @@ import ( "github.com/hashicorp/vault/vault" ) +func handleSysRenew(core *vault.Core) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "PUT" { + respondError(w, http.StatusMethodNotAllowed, nil) + return + } + + // Determine the path... + prefix := "/v1/sys/renew/" + if !strings.HasPrefix(r.URL.Path, prefix) { + respondError(w, http.StatusNotFound, nil) + return + } + path := r.URL.Path[len(prefix):] + if path == "" { + respondError(w, http.StatusNotFound, nil) + return + } + + // Parse the request if we can + var req RenewRequest + if err := parseRequest(r, &req); err != nil { + if err != io.EOF { + respondError(w, http.StatusBadRequest, err) + return + } + } + + resp, ok := request(core, w, requestAuth(r, &logical.Request{ + Operation: logical.WriteOperation, + Path: "sys/renew/" + path, + Data: map[string]interface{}{ + "increment": req.Increment, + }, + })) + if !ok { + return + } + + respondLogical(w, r, resp) + }) +} + func handleSysRevoke(core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "PUT" { @@ -71,3 +115,7 @@ func handleSysRevokePrefix(core *vault.Core) http.Handler { respondOk(w, nil) }) } + +type RenewRequest struct { + Increment int `json:"increment"` +} diff --git a/http/sys_lease_test.go b/http/sys_lease_test.go index d91fb5c47..0be16677b 100644 --- a/http/sys_lease_test.go +++ b/http/sys_lease_test.go @@ -1,11 +1,44 @@ package http import ( + "encoding/json" + "net/http" "testing" "github.com/hashicorp/vault/vault" ) +func TestSysRenew(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + // write secret + resp := testHttpPut(t, addr+"/v1/secret/foo", map[string]interface{}{ + "data": "bar", + "lease": "1h", + }) + testResponseStatus(t, resp, 204) + + // read secret + resp, err := http.Get(addr + "/v1/secret/foo") + if err != nil { + t.Fatalf("err: %s", err) + } + + var result struct { + LeaseId string `json:"lease_id"` + } + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&result); err != nil { + t.Fatalf("bad: %s", err) + } + + resp = testHttpPut(t, addr+"/v1/sys/renew/"+result.LeaseId, nil) + testResponseStatus(t, resp, 200) +} + func TestSysRevoke(t *testing.T) { core, _, token := vault.TestCoreUnsealed(t) ln, addr := TestServer(t, core) diff --git a/vault/logical_passthrough.go b/vault/logical_passthrough.go index f792367b4..aff705e67 100644 --- a/vault/logical_passthrough.go +++ b/vault/logical_passthrough.go @@ -93,7 +93,7 @@ func (b *PassthroughBackend) handleRead( if ok { leaseDuration, err := time.ParseDuration(leaseVal) if err == nil { - resp.Secret.Renewable = false + resp.Secret.Renewable = true resp.Secret.Lease = leaseDuration } }