Skip to content

Commit

Permalink
http: support standby redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
armon committed Apr 19, 2015
1 parent f504aca commit 92dadc4
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 7 deletions.
50 changes: 49 additions & 1 deletion http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"

"github.com/hashicorp/vault/logical"
Expand Down Expand Up @@ -62,8 +63,12 @@ func parseRequest(r *http.Request, out interface{}) error {

// request is a helper to perform a request and properly exit in the
// case of an error.
func request(core *vault.Core, w http.ResponseWriter, r *logical.Request) (*logical.Response, bool) {
func request(core *vault.Core, w http.ResponseWriter, reqURL *url.URL, r *logical.Request) (*logical.Response, bool) {
resp, err := core.HandleRequest(r)
if err == vault.ErrStandby {
respondStandby(core, w, reqURL)
return resp, false
}
if respondCommon(w, resp) {
return resp, false
}
Expand All @@ -75,6 +80,49 @@ func request(core *vault.Core, w http.ResponseWriter, r *logical.Request) (*logi
return resp, true
}

// respondStandby is used to trigger a redirect in the case that this Vault is currently a hot standby
func respondStandby(core *vault.Core, w http.ResponseWriter, reqURL *url.URL) {
// Request the leader address
_, advertise, err := core.Leader()
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}

// If there is no leader, generate a 503 error
if advertise == "" {
err = fmt.Errorf("no active Vault instance found")
respondError(w, http.StatusServiceUnavailable, err)
return
}

// Parse the advertise location
advertiseURL, err := url.Parse(advertise)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}

// Generate a redirect URL
redirectURL := url.URL{
Scheme: advertiseURL.Scheme,
Host: advertiseURL.Host,
Path: reqURL.Path,
RawQuery: reqURL.RawQuery,
}

// Ensure there is a scheme, default to https
if redirectURL.Scheme == "" {
redirectURL.Scheme = "https"
}

// If we have an address, redirect! We use a 307 code
// because we don't actually know if its permanent and
// the request method should be preserved.
w.Header().Set("Location", redirectURL.String())
w.WriteHeader(307)
}

// requestAuth adds the token to the logical.Request if it exists.
func requestAuth(r *http.Request, req *logical.Request) *logical.Request {
// Attach the cookie value as the token if we have it
Expand Down
2 changes: 1 addition & 1 deletion http/logical.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func handleLogical(core *vault.Core) http.Handler {
// Make the internal request. We attach the connection info
// as well in case this is an authentication request that requires
// it. Vault core handles stripping this if we need to.
resp, ok := request(core, w, requestAuth(r, &logical.Request{
resp, ok := request(core, w, r.URL, requestAuth(r, &logical.Request{
Operation: op,
Path: path,
Data: req,
Expand Down
71 changes: 71 additions & 0 deletions http/logical_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"
"time"

"github.com/hashicorp/vault/physical"
"github.com/hashicorp/vault/vault"
)

Expand Down Expand Up @@ -66,3 +67,73 @@ func TestLogical_noExist(t *testing.T) {
}
testResponseStatus(t, resp, 404)
}

func TestLogical_StandbyRedirect(t *testing.T) {
ln1, addr1 := TestListener(t)
defer ln1.Close()
ln2, addr2 := TestListener(t)
defer ln2.Close()

// Create an HA Vault
inm := physical.NewInmemHA()
conf := &vault.CoreConfig{Physical: inm, AdvertiseAddr: addr1}
core1, err := vault.NewCore(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
key, root := vault.TestCoreInit(t, core1)
if _, err := core1.Unseal(vault.TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}

// Create a second HA Vault
conf2 := &vault.CoreConfig{Physical: inm, AdvertiseAddr: addr2}
core2, err := vault.NewCore(conf2)
if err != nil {
t.Fatalf("err: %v", err)
}
if _, err := core2.Unseal(vault.TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}

TestServerWithListener(t, ln1, addr1, core1)
TestServerWithListener(t, ln2, addr2, core2)
TestServerAuth(t, addr1, root)

// WRITE to STANDBY
resp := testHttpPut(t, addr2+"/v1/secret/foo", map[string]interface{}{
"data": "bar",
})
testResponseStatus(t, resp, 307)

//// READ to standby
resp, err = http.Get(addr2 + "/v1/auth/token/lookup-self")
if err != nil {
t.Fatalf("err: %s", err)
}

var actual map[string]interface{}
expected := map[string]interface{}{
"renewable": false,
"lease_duration": float64(0),
"data": map[string]interface{}{
"meta": nil,
"num_uses": float64(0),
"path": "auth/token/root",
"policies": []interface{}{"root"},
"display_name": "root",
"id": root,
},
"auth": nil,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
delete(actual, "lease_id")
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v %#v", actual, expected)
}

//// DELETE to standby
resp = testHttpDelete(t, addr2+"/v1/secret/foo")
testResponseStatus(t, resp, 307)
}
2 changes: 1 addition & 1 deletion http/sys_lease.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func handleSysRenew(core *vault.Core) http.Handler {
}
}

resp, ok := request(core, w, requestAuth(r, &logical.Request{
resp, ok := request(core, w, r.URL, requestAuth(r, &logical.Request{
Operation: logical.WriteOperation,
Path: "sys/renew/" + path,
Data: map[string]interface{}{
Expand Down
8 changes: 4 additions & 4 deletions http/sys_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func handleSysListPolicies(core *vault.Core) http.Handler {
return
}

resp, ok := request(core, w, requestAuth(r, &logical.Request{
resp, ok := request(core, w, r.URL, requestAuth(r, &logical.Request{
Operation: logical.ReadOperation,
Path: "sys/policy",
}))
Expand Down Expand Up @@ -64,7 +64,7 @@ func handleSysDeletePolicy(core *vault.Core, w http.ResponseWriter, r *http.Requ
return
}

_, ok := request(core, w, requestAuth(r, &logical.Request{
_, ok := request(core, w, r.URL, requestAuth(r, &logical.Request{
Operation: logical.DeleteOperation,
Path: "sys/policy/" + path,
}))
Expand All @@ -88,7 +88,7 @@ func handleSysReadPolicy(core *vault.Core, w http.ResponseWriter, r *http.Reques
return
}

resp, ok := request(core, w, requestAuth(r, &logical.Request{
resp, ok := request(core, w, r.URL, requestAuth(r, &logical.Request{
Operation: logical.ReadOperation,
Path: "sys/policy/" + path,
}))
Expand Down Expand Up @@ -119,7 +119,7 @@ func handleSysWritePolicy(core *vault.Core, w http.ResponseWriter, r *http.Reque
return
}

_, ok := request(core, w, requestAuth(r, &logical.Request{
_, ok := request(core, w, r.URL, requestAuth(r, &logical.Request{
Operation: logical.WriteOperation,
Path: "sys/policy/" + path,
Data: map[string]interface{}{
Expand Down

0 comments on commit 92dadc4

Please sign in to comment.