mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-22 07:01:09 +02:00
Merge pull request #16 from hashicorp/f-redirect
HTTP Server should handle standby redirects
This commit is contained in:
commit
3eaffc70e8
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
@ -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
|
// request is a helper to perform a request and properly exit in the
|
||||||
// case of an error.
|
// 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, rawReq *http.Request, r *logical.Request) (*logical.Response, bool) {
|
||||||
resp, err := core.HandleRequest(r)
|
resp, err := core.HandleRequest(r)
|
||||||
|
if err == vault.ErrStandby {
|
||||||
|
respondStandby(core, w, rawReq.URL)
|
||||||
|
return resp, false
|
||||||
|
}
|
||||||
if respondCommon(w, resp) {
|
if respondCommon(w, resp) {
|
||||||
return resp, false
|
return resp, false
|
||||||
}
|
}
|
||||||
@ -75,6 +80,49 @@ func request(core *vault.Core, w http.ResponseWriter, r *logical.Request) (*logi
|
|||||||
return resp, true
|
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.
|
// requestAuth adds the token to the logical.Request if it exists.
|
||||||
func requestAuth(r *http.Request, req *logical.Request) *logical.Request {
|
func requestAuth(r *http.Request, req *logical.Request) *logical.Request {
|
||||||
// Attach the cookie value as the token if we have it
|
// Attach the cookie value as the token if we have it
|
||||||
|
@ -56,7 +56,7 @@ func handleLogical(core *vault.Core) http.Handler {
|
|||||||
// Make the internal request. We attach the connection info
|
// Make the internal request. We attach the connection info
|
||||||
// as well in case this is an authentication request that requires
|
// as well in case this is an authentication request that requires
|
||||||
// it. Vault core handles stripping this if we need to.
|
// 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, requestAuth(r, &logical.Request{
|
||||||
Operation: op,
|
Operation: op,
|
||||||
Path: path,
|
Path: path,
|
||||||
Data: req,
|
Data: req,
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/physical"
|
||||||
"github.com/hashicorp/vault/vault"
|
"github.com/hashicorp/vault/vault"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,3 +67,73 @@ func TestLogical_noExist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
testResponseStatus(t, resp, 404)
|
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)
|
||||||
|
}
|
||||||
|
@ -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, requestAuth(r, &logical.Request{
|
||||||
Operation: logical.WriteOperation,
|
Operation: logical.WriteOperation,
|
||||||
Path: "sys/renew/" + path,
|
Path: "sys/renew/" + path,
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
|
@ -15,7 +15,7 @@ func handleSysListPolicies(core *vault.Core) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, ok := request(core, w, requestAuth(r, &logical.Request{
|
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||||
Operation: logical.ReadOperation,
|
Operation: logical.ReadOperation,
|
||||||
Path: "sys/policy",
|
Path: "sys/policy",
|
||||||
}))
|
}))
|
||||||
@ -64,7 +64,7 @@ func handleSysDeletePolicy(core *vault.Core, w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := request(core, w, requestAuth(r, &logical.Request{
|
_, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||||
Operation: logical.DeleteOperation,
|
Operation: logical.DeleteOperation,
|
||||||
Path: "sys/policy/" + path,
|
Path: "sys/policy/" + path,
|
||||||
}))
|
}))
|
||||||
@ -88,7 +88,7 @@ func handleSysReadPolicy(core *vault.Core, w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, ok := request(core, w, requestAuth(r, &logical.Request{
|
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||||
Operation: logical.ReadOperation,
|
Operation: logical.ReadOperation,
|
||||||
Path: "sys/policy/" + path,
|
Path: "sys/policy/" + path,
|
||||||
}))
|
}))
|
||||||
@ -119,7 +119,7 @@ func handleSysWritePolicy(core *vault.Core, w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := request(core, w, requestAuth(r, &logical.Request{
|
_, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||||
Operation: logical.WriteOperation,
|
Operation: logical.WriteOperation,
|
||||||
Path: "sys/policy/" + path,
|
Path: "sys/policy/" + path,
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/hashicorp/vault/vault"
|
"github.com/hashicorp/vault/vault"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServer(t *testing.T, core *vault.Core) (net.Listener, string) {
|
func TestListener(t *testing.T) (net.Listener, string) {
|
||||||
fail := func(format string, args ...interface{}) {
|
fail := func(format string, args ...interface{}) {
|
||||||
panic(fmt.Sprintf(format, args...))
|
panic(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
@ -24,7 +24,10 @@ func TestServer(t *testing.T, core *vault.Core) (net.Listener, string) {
|
|||||||
fail("err: %s", err)
|
fail("err: %s", err)
|
||||||
}
|
}
|
||||||
addr := "http://" + ln.Addr().String()
|
addr := "http://" + ln.Addr().String()
|
||||||
|
return ln, addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerWithListener(t *testing.T, ln net.Listener, addr string, core *vault.Core) {
|
||||||
// Create a muxer to handle our requests so that we can authenticate
|
// Create a muxer to handle our requests so that we can authenticate
|
||||||
// for tests.
|
// for tests.
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@ -36,7 +39,11 @@ func TestServer(t *testing.T, core *vault.Core) (net.Listener, string) {
|
|||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
go server.Serve(ln)
|
go server.Serve(ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer(t *testing.T, core *vault.Core) (net.Listener, string) {
|
||||||
|
ln, addr := TestListener(t)
|
||||||
|
TestServerWithListener(t, ln, addr, core)
|
||||||
return ln, addr
|
return ln, addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user