Port some replication bits to OSS (#2386)

This commit is contained in:
Jeff Mitchell 2017-02-16 15:15:02 -05:00 committed by GitHub
parent 0a9a6d3343
commit 0c39b613c8
28 changed files with 456 additions and 142 deletions

View File

@ -7,7 +7,7 @@ services:
- docker
go:
- 1.8rc2
- 1.8
matrix:
allow_failures:

View File

@ -24,6 +24,11 @@ dev-dynamic: generate
test: generate
CGO_ENABLED=0 VAULT_TOKEN= VAULT_ACC= go test -tags='$(BUILD_TAGS)' $(TEST) $(TESTARGS) -timeout=10m -parallel=4
testcompile: generate
@for pkg in $(TEST) ; do \
go test -v -c -tags='$(BUILD_TAGS)' $$pkg -parallel=4 ; \
done
# testacc runs acceptance tests
testacc: generate
@if [ "$(TEST)" = "./..." ]; then \

View File

@ -56,9 +56,9 @@ All documentation is available on the [Vault website](https://www.vaultproject.i
Developing Vault
--------------------
If you wish to work on Vault itself or any of its built-in systems,
you'll first need [Go](https://www.golang.org) installed on your
machine (version 1.8+ is *required*).
If you wish to work on Vault itself or any of its built-in systems, you'll
first need [Go](https://www.golang.org) installed on your machine (version 1.8+
is *required*).
For local dev first make sure Go is properly installed, including setting up a
[GOPATH](https://golang.org/doc/code.html#GOPATH). Next, clone this repository

View File

@ -421,7 +421,7 @@ seed_provider:
parameters:
# seeds is actually a comma-delimited list of addresses.
# Ex: "<ip1>,<ip2>,<ip3>"
- seeds: "172.17.0.2"
- seeds: "172.17.0.3"
# For workloads with more data than can fit in memory, Cassandra's
# bottleneck will be reads that need to fetch data from
@ -572,7 +572,7 @@ ssl_storage_port: 7001
#
# Setting listen_address to 0.0.0.0 is always wrong.
#
listen_address: 172.17.0.2
listen_address: 172.17.0.3
# Set listen_address OR listen_interface, not both. Interfaces must correspond
# to a single address, IP aliasing is not supported.
@ -586,7 +586,7 @@ listen_address: 172.17.0.2
# Address to broadcast to other Cassandra nodes
# Leaving this blank will set it to the same value as listen_address
broadcast_address: 172.17.0.2
broadcast_address: 172.17.0.3
# When using multiple physical network interfaces, set this
# to true to listen on broadcast_address in addition to
@ -668,7 +668,7 @@ rpc_port: 9160
# be set to 0.0.0.0. If left blank, this will be set to the value of
# rpc_address. If rpc_address is set to 0.0.0.0, broadcast_rpc_address must
# be set.
broadcast_rpc_address: 172.17.0.2
broadcast_rpc_address: 172.17.0.3
# enable or disable keepalive on rpc/native connections
rpc_keepalive: true

13
helper/consts/error.go Normal file
View File

@ -0,0 +1,13 @@
package consts
import "errors"
var (
// ErrSealed is returned if an operation is performed on a sealed barrier.
// No operation is expected to succeed before unsealing
ErrSealed = errors.New("Vault is sealed")
// ErrStandby is returned if an operation is performed on a standby Vault.
// No operation is expected to succeed until active.
ErrStandby = errors.New("Vault is in standby mode")
)

View File

@ -9,6 +9,7 @@ import (
"strings"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/duration"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
@ -206,11 +207,11 @@ func handleRequestForwarding(core *vault.Core, handler http.Handler) http.Handle
// case of an error.
func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *logical.Request) (*logical.Response, bool) {
resp, err := core.HandleRequest(r)
if errwrap.Contains(err, vault.ErrStandby.Error()) {
if errwrap.Contains(err, consts.ErrStandby.Error()) {
respondStandby(core, w, rawReq.URL)
return resp, false
}
if respondErrorCommon(w, resp, err) {
if respondErrorCommon(w, r, resp, err) {
return resp, false
}
@ -310,20 +311,7 @@ func requestWrapInfo(r *http.Request, req *logical.Request) (*logical.Request, e
}
func respondError(w http.ResponseWriter, status int, err error) {
// Adjust status code when sealed
if errwrap.Contains(err, vault.ErrSealed.Error()) {
status = http.StatusServiceUnavailable
}
// Adjust status code on
if errwrap.Contains(err, "http: request body too large") {
status = http.StatusRequestEntityTooLarge
}
// Allow HTTPCoded error passthrough to specify a code
if t, ok := err.(logical.HTTPCodedError); ok {
status = t.Code()
}
logical.AdjustErrorStatusCode(&status, err)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(status)
@ -337,42 +325,13 @@ func respondError(w http.ResponseWriter, status int, err error) {
enc.Encode(resp)
}
func respondErrorCommon(w http.ResponseWriter, resp *logical.Response, err error) bool {
// If there are no errors return
if err == nil && (resp == nil || !resp.IsError()) {
func respondErrorCommon(w http.ResponseWriter, req *logical.Request, resp *logical.Response, err error) bool {
statusCode, newErr := logical.RespondErrorCommon(req, resp, err)
if newErr == nil && statusCode == 0 {
return false
}
// Start out with internal server error since in most of these cases there
// won't be a response so this won't be overridden
statusCode := http.StatusInternalServerError
// If we actually have a response, start out with bad request
if resp != nil {
statusCode = http.StatusBadRequest
}
// Now, check the error itself; if it has a specific logical error, set the
// appropriate code
if err != nil {
switch {
case errwrap.ContainsType(err, new(vault.StatusBadRequest)):
statusCode = http.StatusBadRequest
case errwrap.Contains(err, logical.ErrPermissionDenied.Error()):
statusCode = http.StatusForbidden
case errwrap.Contains(err, logical.ErrUnsupportedOperation.Error()):
statusCode = http.StatusMethodNotAllowed
case errwrap.Contains(err, logical.ErrUnsupportedPath.Error()):
statusCode = http.StatusNotFound
case errwrap.Contains(err, logical.ErrInvalidRequest.Error()):
statusCode = http.StatusBadRequest
}
}
if resp != nil && resp.IsError() {
err = fmt.Errorf("%s", resp.Data["error"].(string))
}
respondError(w, statusCode, err)
respondError(w, statusCode, newErr)
return true
}

View File

@ -9,6 +9,7 @@ import (
"testing"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
)
@ -80,6 +81,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -88,6 +90,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -96,6 +99,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"secret/": map[string]interface{}{
@ -105,6 +109,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -113,6 +118,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -121,6 +127,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -223,7 +230,7 @@ func TestHandler_error(t *testing.T) {
// vault.ErrSealed is a special case
w3 := httptest.NewRecorder()
respondError(w3, 400, vault.ErrSealed)
respondError(w3, 400, consts.ErrSealed)
if w3.Code != 503 {
t.Fatalf("expected 503, got %d", w3.Code)

View File

@ -35,7 +35,7 @@ func handleHelp(core *vault.Core, w http.ResponseWriter, req *http.Request) {
resp, err := core.HandleRequest(lreq)
if err != nil {
respondErrorCommon(w, resp, err)
respondErrorCommon(w, lreq, resp, err)
return
}

View File

@ -109,40 +109,13 @@ func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback Prepa
// 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.
// it. Vault core handles stripping this if we need to. This also
// handles all error cases; if we hit respondLogical, the request is a
// success.
resp, ok := request(core, w, r, req)
if !ok {
return
}
switch {
case req.Operation == logical.ReadOperation:
if resp == nil {
respondError(w, http.StatusNotFound, nil)
return
}
// Basically: if we have empty "keys" or no keys at all, 404. This
// provides consistency with GET.
case req.Operation == logical.ListOperation && resp.WrapInfo == nil:
if resp == nil || len(resp.Data) == 0 {
respondError(w, http.StatusNotFound, nil)
return
}
keysRaw, ok := resp.Data["keys"]
if !ok || keysRaw == nil {
respondError(w, http.StatusNotFound, nil)
return
}
keys, ok := keysRaw.([]string)
if !ok {
respondError(w, http.StatusInternalServerError, nil)
return
}
if len(keys) == 0 {
respondError(w, http.StatusNotFound, nil)
return
}
}
// Build the proper response
respondLogical(w, r, req, dataOnly, resp)

View File

@ -101,7 +101,7 @@ func TestLogical_StandbyRedirect(t *testing.T) {
// Attempt to fix raciness in this test by giving the first core a chance
// to grab the lock
time.Sleep(time.Second)
time.Sleep(2 * time.Second)
// Create a second HA Vault
conf2 := &vault.CoreConfig{

View File

@ -35,6 +35,7 @@ func TestSysAudit(t *testing.T) {
"type": "noop",
"description": "",
"options": map[string]interface{}{},
"local": false,
},
},
"noop/": map[string]interface{}{
@ -42,6 +43,7 @@ func TestSysAudit(t *testing.T) {
"type": "noop",
"description": "",
"options": map[string]interface{}{},
"local": false,
},
}
testResponseStatus(t, resp, 200)

View File

@ -32,6 +32,7 @@ func TestSysAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"token/": map[string]interface{}{
@ -41,6 +42,7 @@ func TestSysAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -83,6 +85,7 @@ func TestSysEnableAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"token/": map[string]interface{}{
"description": "token based credentials",
@ -91,6 +94,7 @@ func TestSysEnableAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"foo/": map[string]interface{}{
@ -100,6 +104,7 @@ func TestSysEnableAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"token/": map[string]interface{}{
"description": "token based credentials",
@ -108,6 +113,7 @@ func TestSysEnableAuth(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -153,6 +159,7 @@ func TestSysDisableAuth(t *testing.T) {
},
"description": "token based credentials",
"type": "token",
"local": false,
},
},
"token/": map[string]interface{}{
@ -162,6 +169,7 @@ func TestSysDisableAuth(t *testing.T) {
},
"description": "token based credentials",
"type": "token",
"local": false,
},
}
testResponseStatus(t, resp, 200)

View File

@ -33,6 +33,7 @@ func TestSysMounts(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -41,6 +42,7 @@ func TestSysMounts(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -49,6 +51,7 @@ func TestSysMounts(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"secret/": map[string]interface{}{
@ -58,6 +61,7 @@ func TestSysMounts(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -66,6 +70,7 @@ func TestSysMounts(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -74,6 +79,7 @@ func TestSysMounts(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -114,6 +120,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -122,6 +129,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -130,6 +138,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -138,6 +147,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"foo/": map[string]interface{}{
@ -147,6 +157,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -155,6 +166,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -163,6 +175,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -171,6 +184,7 @@ func TestSysMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -233,6 +247,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -241,6 +256,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -249,6 +265,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -257,6 +274,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"bar/": map[string]interface{}{
@ -266,6 +284,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -274,6 +293,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -282,6 +302,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -290,6 +311,7 @@ func TestSysRemount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -333,6 +355,7 @@ func TestSysUnmount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -341,6 +364,7 @@ func TestSysUnmount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -349,6 +373,7 @@ func TestSysUnmount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"secret/": map[string]interface{}{
@ -358,6 +383,7 @@ func TestSysUnmount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -366,6 +392,7 @@ func TestSysUnmount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -374,6 +401,7 @@ func TestSysUnmount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -414,6 +442,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -422,6 +451,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -430,6 +460,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -438,6 +469,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"foo/": map[string]interface{}{
@ -447,6 +479,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -455,6 +488,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -463,6 +497,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -471,6 +506,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}
testResponseStatus(t, resp, 200)
@ -532,6 +568,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("259196400"),
"max_lease_ttl": json.Number("259200000"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -540,6 +577,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -548,6 +586,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -556,6 +595,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
},
"foo/": map[string]interface{}{
@ -565,6 +605,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("259196400"),
"max_lease_ttl": json.Number("259200000"),
},
"local": false,
},
"secret/": map[string]interface{}{
"description": "generic secret storage",
@ -573,6 +614,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging",
@ -581,6 +623,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -589,6 +632,7 @@ func TestSysTuneMount(t *testing.T) {
"default_lease_ttl": json.Number("0"),
"max_lease_ttl": json.Number("0"),
},
"local": false,
},
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/vault"
)
@ -19,6 +20,13 @@ func handleSysRekeyInit(core *vault.Core, recovery bool) http.Handler {
return
}
repState := core.ReplicationState()
if repState == consts.ReplicationSecondary {
respondError(w, http.StatusBadRequest,
fmt.Errorf("rekeying can only be performed on the primary cluster when replication is activated"))
return
}
switch {
case recovery && !core.SealAccess().RecoveryKeySupported():
respondError(w, http.StatusBadRequest, fmt.Errorf("recovery rekeying not supported"))
@ -108,7 +116,7 @@ func handleSysRekeyInitPut(core *vault.Core, recovery bool, w http.ResponseWrite
// Right now we don't support this, but the rest of the code is ready for
// when we do, hence the check below for this to be false if
// StoredShares is greater than zero
if core.SealAccess().StoredKeysSupported() {
if core.SealAccess().StoredKeysSupported() && !recovery {
respondError(w, http.StatusBadRequest, fmt.Errorf("rekeying of barrier not supported when stored key support is available"))
return
}

View File

@ -8,6 +8,7 @@ import (
"net/http"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
"github.com/hashicorp/vault/version"
@ -126,7 +127,7 @@ func handleSysUnseal(core *vault.Core) http.Handler {
case errwrap.Contains(err, vault.ErrBarrierInvalidKey.Error()):
case errwrap.Contains(err, vault.ErrBarrierNotInit.Error()):
case errwrap.Contains(err, vault.ErrBarrierSealed.Error()):
case errwrap.Contains(err, vault.ErrStandby.Error()):
case errwrap.Contains(err, consts.ErrStandby.Error()):
default:
respondError(w, http.StatusInternalServerError, err)
return

View File

@ -8,7 +8,7 @@ import (
// is present on the Request structure for credential backends.
type Connection struct {
// RemoteAddr is the network address that sent the request.
RemoteAddr string
RemoteAddr string `json:"remote_addr"`
// ConnState is the TLS connection state if applicable.
ConnState *tls.ConnectionState

View File

@ -21,3 +21,27 @@ func (e *codedError) Error() string {
func (e *codedError) Code() int {
return e.code
}
// Struct to identify user input errors. This is helpful in responding the
// appropriate status codes to clients from the HTTP endpoints.
type StatusBadRequest struct {
Err string
}
// Implementing error interface
func (s *StatusBadRequest) Error() string {
return s.Err
}
// This is a new type declared to not cause potential compatibility problems if
// the logic around the HTTPCodedError interface changes; in particular for
// logical request paths it is basically ignored, and changing that behavior
// might cause unforseen issues.
type ReplicationCodedError struct {
Msg string
Code int
}
func (r *ReplicationCodedError) Error() string {
return r.Msg
}

111
logical/response_util.go Normal file
View File

@ -0,0 +1,111 @@
package logical
import (
"errors"
"fmt"
"net/http"
"github.com/hashicorp/errwrap"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/helper/consts"
)
// RespondErrorCommon pulls most of the functionality from http's
// respondErrorCommon and some of http's handleLogical and makes it available
// to both the http package and elsewhere.
func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) {
if err == nil && (resp == nil || !resp.IsError()) {
switch {
case req.Operation == ReadOperation:
if resp == nil {
return http.StatusNotFound, nil
}
// Basically: if we have empty "keys" or no keys at all, 404. This
// provides consistency with GET.
case req.Operation == ListOperation && resp.WrapInfo == nil:
if resp == nil || len(resp.Data) == 0 {
return http.StatusNotFound, nil
}
keysRaw, ok := resp.Data["keys"]
if !ok || keysRaw == nil {
return http.StatusNotFound, nil
}
keys, ok := keysRaw.([]string)
if !ok {
return http.StatusInternalServerError, nil
}
if len(keys) == 0 {
return http.StatusNotFound, nil
}
}
return 0, nil
}
if errwrap.ContainsType(err, new(ReplicationCodedError)) {
var allErrors error
codedErr := errwrap.GetType(err, new(ReplicationCodedError)).(*ReplicationCodedError)
errwrap.Walk(err, func(inErr error) {
newErr, ok := inErr.(*ReplicationCodedError)
if !ok {
allErrors = multierror.Append(allErrors, newErr)
}
})
if allErrors != nil {
return codedErr.Code, multierror.Append(errors.New(fmt.Sprintf("errors from both primary and secondary; primary error was %v; secondary errors follow", codedErr.Msg)), allErrors)
}
return codedErr.Code, errors.New(codedErr.Msg)
}
// Start out with internal server error since in most of these cases there
// won't be a response so this won't be overridden
statusCode := http.StatusInternalServerError
// If we actually have a response, start out with bad request
if resp != nil {
statusCode = http.StatusBadRequest
}
// Now, check the error itself; if it has a specific logical error, set the
// appropriate code
if err != nil {
switch {
case errwrap.ContainsType(err, new(StatusBadRequest)):
statusCode = http.StatusBadRequest
case errwrap.Contains(err, ErrPermissionDenied.Error()):
statusCode = http.StatusForbidden
case errwrap.Contains(err, ErrUnsupportedOperation.Error()):
statusCode = http.StatusMethodNotAllowed
case errwrap.Contains(err, ErrUnsupportedPath.Error()):
statusCode = http.StatusNotFound
case errwrap.Contains(err, ErrInvalidRequest.Error()):
statusCode = http.StatusBadRequest
}
}
if resp != nil && resp.IsError() {
err = fmt.Errorf("%s", resp.Data["error"].(string))
}
return statusCode, err
}
// AdjustErrorStatusCode adjusts the status that will be sent in error
// conditions in a way that can be shared across http's respondError and other
// locations.
func AdjustErrorStatusCode(status *int, err error) {
// Adjust status code when sealed
if errwrap.Contains(err, consts.ErrSealed.Error()) {
*status = http.StatusServiceUnavailable
}
// Adjust status code on
if errwrap.Contains(err, "http: request body too large") {
*status = http.StatusRequestEntityTooLarge
}
// Allow HTTPCoded error passthrough to specify a code
if t, ok := err.(HTTPCodedError); ok {
*status = t.Code()
}
}

View File

@ -10,7 +10,7 @@ RUN apt-get update -y && apt-get install --no-install-recommends -y -q \
git mercurial bzr \
&& rm -rf /var/lib/apt/lists/*
ENV GOVERSION 1.8rc3
ENV GOVERSION 1.8
RUN mkdir /goroot && mkdir /gopath
RUN curl https://storage.googleapis.com/golang/go${GOVERSION}.linux-amd64.tar.gz \
| tar xvzf - -C /goroot --strip-components=1

View File

@ -10,6 +10,7 @@ import (
"testing"
"time"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/logformat"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical"
@ -100,7 +101,7 @@ func TestCluster_ListenForRequests(t *testing.T) {
checkListenersFunc := func(expectFail bool) {
tlsConfig, err := cores[0].ClusterTLSConfig()
if err != nil {
if err.Error() != ErrSealed.Error() {
if err.Error() != consts.ErrSealed.Error() {
t.Fatal(err)
}
tlsConfig = lastTLSConfig

View File

@ -60,14 +60,6 @@ const (
)
var (
// ErrSealed is returned if an operation is performed on
// a sealed barrier. No operation is expected to succeed before unsealing
ErrSealed = errors.New("Vault is sealed")
// ErrStandby is returned if an operation is performed on
// a standby Vault. No operation is expected to succeed until active.
ErrStandby = errors.New("Vault is in standby mode")
// ErrAlreadyInit is returned if the core is already
// initialized. This prevents a re-initialization.
ErrAlreadyInit = errors.New("Vault is already initialized")
@ -519,10 +511,10 @@ func (c *Core) LookupToken(token string) (*TokenEntry, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
// Many tests don't have a token store running
@ -657,7 +649,7 @@ func (c *Core) Leader() (isLeader bool, leaderAddr string, err error) {
// Check if sealed
if c.sealed {
return false, "", ErrSealed
return false, "", consts.ErrSealed
}
// Check if HA enabled
@ -1600,6 +1592,14 @@ func (c *Core) emitMetrics(stopCh chan struct{}) {
}
}
func (c *Core) ReplicationState() consts.ReplicationState {
var state consts.ReplicationState
c.clusterParamsLock.RLock()
state = c.replicationState
c.clusterParamsLock.RUnlock()
return state
}
func (c *Core) SealAccess() *SealAccess {
sa := &SealAccess{}
sa.SetSeal(c.seal)

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/logformat"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical"
@ -198,7 +199,7 @@ func TestCore_Route_Sealed(t *testing.T) {
Path: "sys/mounts",
}
_, err := c.HandleRequest(req)
if err != ErrSealed {
if err != consts.ErrSealed {
t.Fatalf("err: %v", err)
}
@ -1541,7 +1542,7 @@ func testCore_Standby_Common(t *testing.T, inm physical.Backend, inmha physical.
// Request should fail in standby mode
_, err = core2.HandleRequest(req)
if err != ErrStandby {
if err != consts.ErrStandby {
t.Fatalf("err: %v", err)
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/helper/xor"
"github.com/hashicorp/vault/shamir"
@ -34,10 +35,10 @@ func (c *Core) GenerateRootProgress() (int, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return 0, ErrSealed
return 0, consts.ErrSealed
}
if c.standby {
return 0, ErrStandby
return 0, consts.ErrStandby
}
c.generateRootLock.Lock()
@ -52,10 +53,10 @@ func (c *Core) GenerateRootConfiguration() (*GenerateRootConfig, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
c.generateRootLock.Lock()
@ -101,10 +102,10 @@ func (c *Core) GenerateRootInit(otp, pgpKey string) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
return consts.ErrSealed
}
if c.standby {
return ErrStandby
return consts.ErrStandby
}
c.generateRootLock.Lock()
@ -170,10 +171,10 @@ func (c *Core) GenerateRootUpdate(key []byte, nonce string) (*GenerateRootResult
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
c.generateRootLock.Lock()
@ -308,10 +309,10 @@ func (c *Core) GenerateRootCancel() error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
return consts.ErrSealed
}
if c.standby {
return ErrStandby
return consts.ErrStandby
}
c.generateRootLock.Lock()

View File

@ -7,6 +7,7 @@ import (
"fmt"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/physical"
@ -44,10 +45,10 @@ func (c *Core) RekeyThreshold(recovery bool) (int, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return 0, ErrSealed
return 0, consts.ErrSealed
}
if c.standby {
return 0, ErrStandby
return 0, consts.ErrStandby
}
c.rekeyLock.RLock()
@ -72,10 +73,10 @@ func (c *Core) RekeyProgress(recovery bool) (int, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return 0, ErrSealed
return 0, consts.ErrSealed
}
if c.standby {
return 0, ErrStandby
return 0, consts.ErrStandby
}
c.rekeyLock.RLock()
@ -92,10 +93,10 @@ func (c *Core) RekeyConfig(recovery bool) (*SealConfig, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
c.rekeyLock.Lock()
@ -146,10 +147,10 @@ func (c *Core) BarrierRekeyInit(config *SealConfig) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
return consts.ErrSealed
}
if c.standby {
return ErrStandby
return consts.ErrStandby
}
c.rekeyLock.Lock()
@ -196,10 +197,10 @@ func (c *Core) RecoveryRekeyInit(config *SealConfig) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
return consts.ErrSealed
}
if c.standby {
return ErrStandby
return consts.ErrStandby
}
c.rekeyLock.Lock()
@ -240,10 +241,10 @@ func (c *Core) BarrierRekeyUpdate(key []byte, nonce string) (*RekeyResult, error
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
// Verify the key length
@ -422,10 +423,10 @@ func (c *Core) RecoveryRekeyUpdate(key []byte, nonce string) (*RekeyResult, erro
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
// Verify the key length
@ -589,10 +590,10 @@ func (c *Core) RekeyCancel(recovery bool) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
return consts.ErrSealed
}
if c.standby {
return ErrStandby
return consts.ErrStandby
}
c.rekeyLock.Lock()
@ -615,10 +616,10 @@ func (c *Core) RekeyRetrieveBackup(recovery bool) (*RekeyBackup, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
c.rekeyLock.RLock()
@ -652,10 +653,10 @@ func (c *Core) RekeyDeleteBackup(recovery bool) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
return consts.ErrSealed
}
if c.standby {
return ErrStandby
return consts.ErrStandby
}
c.rekeyLock.Lock()

View File

@ -7,6 +7,7 @@ import (
"github.com/armon/go-metrics"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/helper/policyutil"
"github.com/hashicorp/vault/helper/strutil"
@ -18,10 +19,10 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
return nil, consts.ErrSealed
}
if c.standby {
return nil, ErrStandby
return nil, consts.ErrStandby
}
// Allowing writing to a path ending in / makes it extremely difficult to

View File

@ -13,6 +13,7 @@ import (
"github.com/SermoDigital/jose/jws"
"github.com/SermoDigital/jose/jwt"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
)
@ -284,10 +285,10 @@ func (c *Core) ValidateWrappingToken(req *logical.Request) (bool, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return false, ErrSealed
return false, consts.ErrSealed
}
if c.standby {
return false, ErrStandby
return false, consts.ErrStandby
}
te, err := c.tokenStore.Lookup(token)

View File

@ -0,0 +1,149 @@
---
layout: "docs"
page_title: "Replication"
sidebar_current: "docs-internals-replication"
description: |-
Learn about the details of multi-datacenter replication within Vault.
---
# Replication (Vault Enterprise)
Vault Enterprise 0.7 adds support for multi-datacenter replication. Before
using this feature, it is useful to understand the intended use cases, design
goals, and high level architecture.
Replication is based on a primary/secondary (1:N) model with asynchronous
replication, focusing on high availability for global deployments. The
trade-offs made in the design and implementation of replication reflect these
high level goals.
# Use Cases
Vault replication is based on a number of common use cases:
* **Multi-Datacenter Deployments**: A common challenge is providing Vault to
applications across many datacenters in a highly-available manner. Running a
single Vault cluster imposes high latency of access for remote clients,
availability loss or outages during connectivity failures, and limits
scalability.
* **Backup Sites**: Implementing a robust business continuity plan around the
loss of a primary datacenter requires the ability to quickly and easily fail
to a hot backup site.
* **Scaling Throughput**: Applications that use Vault for
Encryption-as-a-Service or cryptographic offload may generate a very high
volume of requests for Vault. Replicating keys between multiple clusters
allows load to be distributed across additional servers to scale request
throughput.
# Design Goals
Based on the use cases for Vault Replication, we had a number of design goals
for the implementation:
* **Availability**: Global deployments of Vault require high levels of
availability, and can tolerate reduced consistency. During full connectivity,
replication is nearly real-time between the primary and secondary clusters.
Degraded connectivity between a primary and secondary does not impact the
primary's ability to service requests, and the secondary will continue to
service reads on last-known data.
* **Conflict Free**: Certain replication techniques allow for potential write
conflicts to take place. Particularly, any active/active configuration where
writes are allowed to multiple sites require a conflict resolution strategy.
This varies from techniques that allow for data loss like last-write-wins, or
techniques that require manual operator resolution like allowing multiple
values per key. We avoid the possibility of conflicts to ensure there is no
data loss or manual intervention required.
* **Transparent to Clients**: Vault replication should be transparent to
clients of Vault, so that existing thin clients work unmodified. The Vault
servers handle the logic of request forwarding to the primary when necessary,
and multi-hop routing is performed internally to ensure requests are
processed.
* **Simple to Operate**: Operating a replicated cluster should be simple to
avoid administrative overhead and potentially introducing security gaps.
Setup of replication is very simple, and secondaries can handle being
arbitrarily behind the primary, avoiding the need for operator intervention
to copy data or snapshot the primary.
# Architecture
The architecture of Vault replication is based on the design goals, focusing on
the intended use cases. When replication is enabled, a cluster is set as either
a _primary_ or _secondary_. The primary cluster is authoritative, and is the
only cluster allowed to perform actions that write to the underlying data
storage, such as modifying policies or secrets. Secondary clusters can service
all other operations, such as reading secrets or sending data through
`transit`, and forward any writes to the primary cluster. Disallowing multiple
primaries ensures the cluster is conflict free and has an authoritative state.
The primary cluster uses log shipping to replicate changes to all of the
secondaries. This ensures writes are visible globally in near real-time when
there is full network connectivity. If a secondary is down or unable to
communicate with the primary, writes are not blocked on the primary and reads
are still serviced on the secondary. This ensures the availability of Vault.
When the secondary is initialized or recovers from degraded connectivity it
will automatically reconcile with the primary.
Lastly, clients can speak to any Vault server without a thick client. If a
client is communicating with a standby instance, the request is automatically
forwarded to a active instance. Secondary clusters will service reads locally
and forward any write requests to the primary cluster. The primary cluster is
able to service all request types.
An important optimization Vault makes is to avoid replication of tokens or
leases between clusters. Policies and secrets are the minority of data managed
by Vault and tend to be relatively stable. Tokens and leases are much more
dynamic, as they are created and expire rapidly. Keeping tokens and leases
locally reduces the amount of data that needs to be replicated, and distributes
the work of TTL management between the clusters. The caveat is that clients
will need to re-authenticate if they switch the Vault cluster they are
communicating with.
# Implementation Details
It is important to understand the high-level architecture of replication to
ensure the trade-offs are appropriate for your use case. The implementation
details may be useful for those who are curious or want to understand more
about the performance characteristics or failure scenarios.
Using replication requires a storage backend that supports transactional
updates, such as Consul. This allows multiple key/value updates to be
performed atomically. Replication uses this to maintain a
[Write-Ahead-Log][wal] (WAL) of all updates, so that the key update happens
atomically with the WAL entry creation. The WALs are then used to perform log
shipping between the Vault clusters. When a secondary is closely synchronized
with a primary, Vault directly streams new WALs to be applied, providing near
real-time replication. A bounded set of WALs are maintained for the
secondaries, and older WALs are garbage collected automatically.
When a secondary is initialized or is too far behind the primary there may not
be enough WALs to synchronize. To handle this scenario, Vault maintains a
[merkle index][merkle] of the encrypted keys. Any time a key is updated or
deleted, the merkle index is updated to reflect the change. When a secondary
needs to reconcile with a primary, they compare their merkle indexes to
determine which keys are out of sync. The structure of the index allows this to
be done very efficiently, usually requiring only two round trips and a small
amount of data. The secondary uses this information to reconcile and then
switches back into WAL streaming mode.
Performance is an important concern for Vault, so WAL entries are batched and
the merkle index is not flushed to disk with every operation. Instead, the
index is updated in memory for every operation and asynchronously flushed to
disk. As a result, a crash or power loss may cause the merkle index to become
out of sync with the underlying keys. Vault uses the [ARIES][aries] recovery
algorithm to ensure the consistency of the index under those failure
conditions.
Log shipping traditionally requires the WAL stream to be synchronized, which
can introduce additional complexity when a new primary cluster is promoted.
Vault uses the merkle index as the source of truth, allowing the WAL streams to
be completely distinct and unsynchronized. This simplifies administration of
Vault Replication for operators.
[wal]: https://en.wikipedia.org/wiki/Write-ahead_logging
[merkle]: https://en.wikipedia.org/wiki/Merkle_tree
[aries]: https://en.wikipedia.org/wiki/Algorithms_for_Recovery_and_Isolation_Exploiting_Semantics

View File

@ -32,6 +32,10 @@
<li<%= sidebar_current("docs-internals-rotation") %>>
<a href="/docs/internals/rotation.html">Key Rotation</a>
</li>
<li<%= sidebar_current("docs-internals-replication") %>>
<a href="/docs/internals/replication.html">Replication</a>
</li>
</ul>
</li>