Wrapping enhancements (#1927)
This commit is contained in:
parent
c474af7d24
commit
b45a481365
|
@ -327,17 +327,19 @@ func (c *Client) NewRequest(method, path string) *Request {
|
|||
Params: make(map[string][]string),
|
||||
}
|
||||
|
||||
var lookupPath string
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "/v1/")
|
||||
case strings.HasPrefix(path, "v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "v1/")
|
||||
default:
|
||||
lookupPath = path
|
||||
}
|
||||
if c.wrappingLookupFunc != nil {
|
||||
var lookupPath string
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "/v1/")
|
||||
case strings.HasPrefix(path, "v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "v1/")
|
||||
default:
|
||||
lookupPath = path
|
||||
}
|
||||
req.WrapTTL = c.wrappingLookupFunc(method, lookupPath)
|
||||
} else {
|
||||
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
|
||||
}
|
||||
|
||||
return req
|
||||
|
|
|
@ -3,6 +3,8 @@ package api
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
)
|
||||
|
@ -11,6 +13,26 @@ const (
|
|||
wrappedResponseLocation = "cubbyhole/response"
|
||||
)
|
||||
|
||||
var (
|
||||
// The default TTL that will be used with `sys/wrapping/wrap`, can be
|
||||
// changed
|
||||
DefaultWrappingTTL = "5m"
|
||||
|
||||
// The default function used if no other function is set, which honors the
|
||||
// env var and wraps `sys/wrapping/wrap`
|
||||
DefaultWrappingLookupFunc = func(operation, path string) string {
|
||||
if os.Getenv(EnvVaultWrapTTL) != "" {
|
||||
return os.Getenv(EnvVaultWrapTTL)
|
||||
}
|
||||
|
||||
if (operation == "PUT" || operation == "POST") && path == "sys/wrapping/wrap" {
|
||||
return DefaultWrappingTTL
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
)
|
||||
|
||||
// Logical is used to perform logical backend operations on Vault.
|
||||
type Logical struct {
|
||||
c *Client
|
||||
|
@ -96,10 +118,39 @@ func (c *Logical) Delete(path string) (*Secret, error) {
|
|||
}
|
||||
|
||||
func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) {
|
||||
origToken := c.c.Token()
|
||||
defer c.c.SetToken(origToken)
|
||||
var data map[string]interface{}
|
||||
if wrappingToken != "" {
|
||||
data = map[string]interface{}{
|
||||
"token": wrappingToken,
|
||||
}
|
||||
}
|
||||
|
||||
c.c.SetToken(wrappingToken)
|
||||
r := c.c.NewRequest("PUT", "/v1/sys/wrapping/unwrap")
|
||||
if err := r.SetJSONBody(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil && resp.StatusCode != 404 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK: // New method is supported
|
||||
return ParseSecret(resp.Body)
|
||||
case http.StatusNotFound: // Fall back to old method
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if wrappingToken == "" {
|
||||
origToken := c.c.Token()
|
||||
defer c.c.SetToken(origToken)
|
||||
c.c.SetToken(wrappingToken)
|
||||
}
|
||||
|
||||
secret, err := c.Read(wrappedResponseLocation)
|
||||
if err != nil {
|
||||
|
|
|
@ -177,7 +177,10 @@ func TestRekey_init_pgp(t *testing.T) {
|
|||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
sysBackend := vault.NewSystemBackend(core, bc)
|
||||
sysBackend, err := vault.NewSystemBackend(core, bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &RekeyCommand{
|
||||
|
|
|
@ -30,18 +30,22 @@ func (c *UnwrapCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
args = flags.Args()
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
c.Ui.Error("Unwrap expects one argument: the ID of the wrapping token")
|
||||
flags.Usage()
|
||||
return 1
|
||||
}
|
||||
var tokenID string
|
||||
|
||||
tokenID := args[0]
|
||||
_, err = uuid.ParseUUID(tokenID)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Given token could not be parsed as a UUID: %s", err))
|
||||
args = flags.Args()
|
||||
switch len(args) {
|
||||
case 0:
|
||||
case 1:
|
||||
tokenID = args[0]
|
||||
_, err = uuid.ParseUUID(tokenID)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Given token could not be parsed as a UUID: %v", err))
|
||||
return 1
|
||||
}
|
||||
default:
|
||||
c.Ui.Error("Unwrap expects zero or one argument (the ID of the wrapping token)")
|
||||
flags.Usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/command/token"
|
||||
|
@ -55,7 +56,7 @@ func PrintRawField(ui cli.Ui, secret *api.Secret, field string) int {
|
|||
case "wrapping_token_ttl":
|
||||
val = secret.WrapInfo.TTL
|
||||
case "wrapping_token_creation_time":
|
||||
val = secret.WrapInfo.CreationTime.String()
|
||||
val = secret.WrapInfo.CreationTime.Format(time.RFC3339Nano)
|
||||
case "wrapped_accessor":
|
||||
val = secret.WrapInfo.WrappedAccessor
|
||||
default:
|
||||
|
|
|
@ -48,7 +48,10 @@ func Handler(core *vault.Core) http.Handler {
|
|||
mux.Handle("/v1/sys/rekey/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, false)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/init", handleRequestForwarding(core, handleSysRekeyInit(core, true)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, true)))
|
||||
mux.Handle("/v1/sys/capabilities-self", handleRequestForwarding(core, handleLogical(core, true, sysCapabilitiesSelfCallback)))
|
||||
mux.Handle("/v1/sys/wrapping/lookup", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
|
||||
mux.Handle("/v1/sys/wrapping/rewrap", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
|
||||
mux.Handle("/v1/sys/wrapping/unwrap", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc)))
|
||||
mux.Handle("/v1/sys/capabilities-self", handleRequestForwarding(core, handleLogical(core, true, nil)))
|
||||
mux.Handle("/v1/sys/", handleRequestForwarding(core, handleLogical(core, true, nil)))
|
||||
mux.Handle("/v1/", handleRequestForwarding(core, handleLogical(core, false, nil)))
|
||||
|
||||
|
@ -58,15 +61,35 @@ func Handler(core *vault.Core) http.Handler {
|
|||
return handler
|
||||
}
|
||||
|
||||
// ClientToken is required in the handler of sys/capabilities-self endpoint in
|
||||
// system backend. But the ClientToken gets obfuscated before the request gets
|
||||
// forwarded to any logical backend. So, setting the ClientToken in the data
|
||||
// field for this request.
|
||||
func sysCapabilitiesSelfCallback(req *logical.Request) error {
|
||||
if req == nil || req.Data == nil {
|
||||
// A lookup on a token that is about to expire returns nil, which means by the
|
||||
// time we can validate a wrapping token lookup will return nil since it will
|
||||
// be revoked after the call. So we have to do the validation here.
|
||||
func wrappingVerificationFunc(core *vault.Core, req *logical.Request) error {
|
||||
if req == nil {
|
||||
return fmt.Errorf("invalid request")
|
||||
}
|
||||
req.Data["token"] = req.ClientToken
|
||||
|
||||
var token string
|
||||
if req.Data != nil && req.Data["token"] != nil {
|
||||
if tokenStr, ok := req.Data["token"].(string); !ok {
|
||||
return fmt.Errorf("could not decode token in request body")
|
||||
} else if tokenStr == "" {
|
||||
return fmt.Errorf("empty token in request body")
|
||||
} else {
|
||||
token = tokenStr
|
||||
}
|
||||
} else {
|
||||
token = req.ClientToken
|
||||
}
|
||||
|
||||
valid, err := core.ValidateWrappingToken(token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error validating wrapping token: %v", err)
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("wrapping token is not valid or does not exist")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
type PrepareRequestFunc func(req *logical.Request) error
|
||||
type PrepareRequestFunc func(*vault.Core, *logical.Request) error
|
||||
|
||||
func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Request, int, error) {
|
||||
// Determine the path...
|
||||
|
@ -99,8 +99,8 @@ func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback Prepa
|
|||
// will have a callback registered to do the needed operations, so
|
||||
// invoke it before proceeding.
|
||||
if prepareRequestCallback != nil {
|
||||
if err := prepareRequestCallback(req); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
if err := prepareRequestCallback(core, req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -160,8 +160,8 @@ func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request
|
|||
}
|
||||
|
||||
// Check if this is a raw response
|
||||
if _, ok := resp.Data[logical.HTTPContentType]; ok {
|
||||
respondRaw(w, r, req.Path, resp)
|
||||
if _, ok := resp.Data[logical.HTTPStatusCode]; ok {
|
||||
respondRaw(w, r, resp)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -197,51 +197,68 @@ func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request
|
|||
// respondRaw is used when the response is using HTTPContentType and HTTPRawBody
|
||||
// to change the default response handling. This is only used for specific things like
|
||||
// returning the CRL information on the PKI backends.
|
||||
func respondRaw(w http.ResponseWriter, r *http.Request, path string, resp *logical.Response) {
|
||||
func respondRaw(w http.ResponseWriter, r *http.Request, resp *logical.Response) {
|
||||
retErr := func(w http.ResponseWriter, err string) {
|
||||
w.Header().Set("X-Vault-Raw-Error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write(nil)
|
||||
}
|
||||
|
||||
// Ensure this is never a secret or auth response
|
||||
if resp.Secret != nil || resp.Auth != nil {
|
||||
respondError(w, http.StatusInternalServerError, nil)
|
||||
retErr(w, "raw responses cannot contain secrets or auth")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the status code
|
||||
statusRaw, ok := resp.Data[logical.HTTPStatusCode]
|
||||
if !ok {
|
||||
respondError(w, http.StatusInternalServerError, nil)
|
||||
retErr(w, "no status code given")
|
||||
return
|
||||
}
|
||||
status, ok := statusRaw.(int)
|
||||
if !ok {
|
||||
respondError(w, http.StatusInternalServerError, nil)
|
||||
retErr(w, "cannot decode status code")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the header
|
||||
nonEmpty := status != http.StatusNoContent
|
||||
|
||||
var contentType string
|
||||
var body []byte
|
||||
|
||||
// Get the content type header; don't require it if the body is empty
|
||||
contentTypeRaw, ok := resp.Data[logical.HTTPContentType]
|
||||
if !ok {
|
||||
respondError(w, http.StatusInternalServerError, nil)
|
||||
if !ok && !nonEmpty {
|
||||
retErr(w, "no content type given")
|
||||
return
|
||||
}
|
||||
contentType, ok := contentTypeRaw.(string)
|
||||
if !ok {
|
||||
respondError(w, http.StatusInternalServerError, nil)
|
||||
return
|
||||
if ok {
|
||||
contentType, ok = contentTypeRaw.(string)
|
||||
if !ok {
|
||||
retErr(w, "cannot decode content type")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get the body
|
||||
bodyRaw, ok := resp.Data[logical.HTTPRawBody]
|
||||
if !ok {
|
||||
respondError(w, http.StatusInternalServerError, nil)
|
||||
return
|
||||
}
|
||||
body, ok := bodyRaw.([]byte)
|
||||
if !ok {
|
||||
respondError(w, http.StatusInternalServerError, nil)
|
||||
return
|
||||
if nonEmpty {
|
||||
// Get the body
|
||||
bodyRaw, ok := resp.Data[logical.HTTPRawBody]
|
||||
if !ok {
|
||||
retErr(w, "no body given")
|
||||
return
|
||||
}
|
||||
body, ok = bodyRaw.([]byte)
|
||||
if !ok {
|
||||
retErr(w, "cannot decode body")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Write the response
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
if contentType != "" {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
}
|
||||
w.WriteHeader(status)
|
||||
w.Write(body)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,350 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
// Test wrapping functionality
|
||||
func TestHTTP_Wrapping(t *testing.T) {
|
||||
handler1 := http.NewServeMux()
|
||||
handler2 := http.NewServeMux()
|
||||
handler3 := http.NewServeMux()
|
||||
|
||||
coreConfig := &vault.CoreConfig{}
|
||||
|
||||
// Chicken-and-egg: Handler needs a core. So we create handlers first, then
|
||||
// add routes chained to a Handler-created handler.
|
||||
cores := vault.TestCluster(t, []http.Handler{handler1, handler2, handler3}, coreConfig, true)
|
||||
for _, core := range cores {
|
||||
defer core.CloseListeners()
|
||||
}
|
||||
handler1.Handle("/", Handler(cores[0].Core))
|
||||
handler2.Handle("/", Handler(cores[1].Core))
|
||||
handler3.Handle("/", Handler(cores[2].Core))
|
||||
|
||||
// make it easy to get access to the active
|
||||
core := cores[0].Core
|
||||
vault.TestWaitActive(t, core)
|
||||
|
||||
root := cores[0].Root
|
||||
|
||||
transport := cleanhttp.DefaultTransport()
|
||||
transport.TLSClientConfig = cores[0].TLSConfig
|
||||
httpClient := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
addr := fmt.Sprintf("https://127.0.0.1:%d", cores[0].Listeners[0].Address.Port)
|
||||
config := api.DefaultConfig()
|
||||
config.Address = addr
|
||||
config.HttpClient = httpClient
|
||||
client, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client.SetToken(root)
|
||||
|
||||
// Write a value that we will use with wrapping for lookup
|
||||
_, err = client.Logical().Write("secret/foo", map[string]interface{}{
|
||||
"zip": "zap",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Set a wrapping lookup function for reads on that path
|
||||
client.SetWrappingLookupFunc(func(operation, path string) string {
|
||||
if operation == "GET" && path == "secret/foo" {
|
||||
return "5m"
|
||||
}
|
||||
|
||||
return api.DefaultWrappingLookupFunc(operation, path)
|
||||
})
|
||||
|
||||
// First test: basic things that should fail, lookup edition
|
||||
// Root token isn't a wrapping token
|
||||
_, err = client.Logical().Write("sys/wrapping/lookup", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
// Not supplied
|
||||
_, err = client.Logical().Write("sys/wrapping/lookup", map[string]interface{}{
|
||||
"foo": "bar",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
// Nonexistent token isn't a wrapping token
|
||||
_, err = client.Logical().Write("sys/wrapping/lookup", map[string]interface{}{
|
||||
"token": "bar",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
// Second: basic things that should fail, unwrap edition
|
||||
// Root token isn't a wrapping token
|
||||
_, err = client.Logical().Unwrap(root)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
// Root token isn't a wrapping token
|
||||
_, err = client.Logical().Write("sys/wrapping/unwrap", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
// Not supplied
|
||||
_, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{
|
||||
"foo": "bar",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
// Nonexistent token isn't a wrapping token
|
||||
_, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{
|
||||
"token": "bar",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
//
|
||||
// Test lookup
|
||||
//
|
||||
|
||||
// Create a wrapping token
|
||||
secret, err := client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secret == nil || secret.WrapInfo == nil {
|
||||
t.Fatal("secret or wrap info is nil")
|
||||
}
|
||||
wrapInfo := secret.WrapInfo
|
||||
|
||||
// Test this twice to ensure no ill effect to the wrapping token as a result of the lookup
|
||||
for i := 0; i < 2; i++ {
|
||||
secret, err = client.Logical().Write("sys/wrapping/lookup", map[string]interface{}{
|
||||
"token": wrapInfo.Token,
|
||||
})
|
||||
if secret == nil || secret.Data == nil {
|
||||
t.Fatal("secret or secret data is nil")
|
||||
}
|
||||
creationTTL, _ := secret.Data["creation_ttl"].(json.Number).Int64()
|
||||
if int(creationTTL) != wrapInfo.TTL {
|
||||
t.Fatalf("mistmatched ttls: %d vs %d", creationTTL, wrapInfo.TTL)
|
||||
}
|
||||
if secret.Data["creation_time"].(string) != wrapInfo.CreationTime.Format(time.RFC3339Nano) {
|
||||
t.Fatalf("mistmatched creation times: %d vs %d", secret.Data["creation_time"].(string), wrapInfo.CreationTime.Format(time.RFC3339Nano))
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Test unwrap
|
||||
//
|
||||
|
||||
// Create a wrapping token
|
||||
secret, err = client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secret == nil || secret.WrapInfo == nil {
|
||||
t.Fatal("secret or wrap info is nil")
|
||||
}
|
||||
wrapInfo = secret.WrapInfo
|
||||
|
||||
// Test unwrap via the client token
|
||||
client.SetToken(wrapInfo.Token)
|
||||
secret, err = client.Logical().Write("sys/wrapping/unwrap", nil)
|
||||
if secret == nil || secret.Data == nil {
|
||||
t.Fatal("secret or secret data is nil")
|
||||
}
|
||||
ret1 := secret
|
||||
// Should be expired and fail
|
||||
_, err = client.Logical().Write("sys/wrapping/unwrap", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
// Create a wrapping token
|
||||
client.SetToken(root)
|
||||
secret, err = client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secret == nil || secret.WrapInfo == nil {
|
||||
t.Fatal("secret or wrap info is nil")
|
||||
}
|
||||
wrapInfo = secret.WrapInfo
|
||||
|
||||
// Test as a separate token
|
||||
secret, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{
|
||||
"token": wrapInfo.Token,
|
||||
})
|
||||
ret2 := secret
|
||||
// Should be expired and fail
|
||||
_, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{
|
||||
"token": wrapInfo.Token,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
// Create a wrapping token
|
||||
secret, err = client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secret == nil || secret.WrapInfo == nil {
|
||||
t.Fatal("secret or wrap info is nil")
|
||||
}
|
||||
wrapInfo = secret.WrapInfo
|
||||
|
||||
// Read response directly
|
||||
client.SetToken(wrapInfo.Token)
|
||||
secret, err = client.Logical().Read("cubbyhole/response")
|
||||
ret3 := secret
|
||||
// Should be expired and fail
|
||||
_, err = client.Logical().Write("cubbyhole/response", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
// Create a wrapping token
|
||||
client.SetToken(root)
|
||||
secret, err = client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secret == nil || secret.WrapInfo == nil {
|
||||
t.Fatal("secret or wrap info is nil")
|
||||
}
|
||||
wrapInfo = secret.WrapInfo
|
||||
|
||||
// Read via Unwrap method
|
||||
secret, err = client.Logical().Unwrap(wrapInfo.Token)
|
||||
ret4 := secret
|
||||
// Should be expired and fail
|
||||
_, err = client.Logical().Unwrap(wrapInfo.Token)
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ret1.Data, map[string]interface{}{
|
||||
"zip": "zap",
|
||||
}) {
|
||||
t.Fatalf("ret1 data did not match expected: %#v", ret1.Data)
|
||||
}
|
||||
if !reflect.DeepEqual(ret2.Data, map[string]interface{}{
|
||||
"zip": "zap",
|
||||
}) {
|
||||
t.Fatalf("ret2 data did not match expected: %#v", ret2.Data)
|
||||
}
|
||||
var ret3Secret api.Secret
|
||||
err = jsonutil.DecodeJSON([]byte(ret3.Data["response"].(string)), &ret3Secret)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(ret3Secret.Data, map[string]interface{}{
|
||||
"zip": "zap",
|
||||
}) {
|
||||
t.Fatalf("ret3 data did not match expected: %#v", ret3Secret.Data)
|
||||
}
|
||||
if !reflect.DeepEqual(ret4.Data, map[string]interface{}{
|
||||
"zip": "zap",
|
||||
}) {
|
||||
t.Fatalf("ret4 data did not match expected: %#v", ret4.Data)
|
||||
}
|
||||
|
||||
//
|
||||
// Custom wrapping
|
||||
//
|
||||
|
||||
client.SetToken(root)
|
||||
data := map[string]interface{}{
|
||||
"zip": "zap",
|
||||
"three": json.Number("2"),
|
||||
}
|
||||
|
||||
// Don't set a request TTL on that path, should fail
|
||||
client.SetWrappingLookupFunc(func(operation, path string) string {
|
||||
return ""
|
||||
})
|
||||
secret, err = client.Logical().Write("sys/wrapping/wrap", data)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
// Re-set the lookup function
|
||||
client.SetWrappingLookupFunc(func(operation, path string) string {
|
||||
if operation == "GET" && path == "secret/foo" {
|
||||
return "5m"
|
||||
}
|
||||
|
||||
return api.DefaultWrappingLookupFunc(operation, path)
|
||||
})
|
||||
secret, err = client.Logical().Write("sys/wrapping/wrap", data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secret, err = client.Logical().Unwrap(secret.WrapInfo.Token)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(data, secret.Data) {
|
||||
t.Fatal("custom wrap did not match expected: %#v", secret.Data)
|
||||
}
|
||||
|
||||
//
|
||||
// Test rewrap
|
||||
//
|
||||
|
||||
// Create a wrapping token
|
||||
secret, err = client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secret == nil || secret.WrapInfo == nil {
|
||||
t.Fatal("secret or wrap info is nil")
|
||||
}
|
||||
wrapInfo = secret.WrapInfo
|
||||
|
||||
// Test rewrapping
|
||||
secret, err = client.Logical().Write("sys/wrapping/rewrap", map[string]interface{}{
|
||||
"token": wrapInfo.Token,
|
||||
})
|
||||
// Should be expired and fail
|
||||
_, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{
|
||||
"token": wrapInfo.Token,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
// Attempt unwrapping the rewrapped token
|
||||
wrapToken := secret.WrapInfo.Token
|
||||
secret, err = client.Logical().Unwrap(wrapToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Should be expired and fail
|
||||
_, err = client.Logical().Unwrap(wrapToken)
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(secret.Data, map[string]interface{}{
|
||||
"zip": "zap",
|
||||
}) {
|
||||
t.Fatalf("secret data did not match expected: %#v", secret.Data)
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bufio"
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault/api"
|
||||
|
@ -51,7 +50,7 @@ func (m *Meta) DefaultWrappingLookupFunc(operation, path string) string {
|
|||
return m.flagWrapTTL
|
||||
}
|
||||
|
||||
return os.Getenv(api.EnvVaultWrapTTL)
|
||||
return api.DefaultWrappingLookupFunc(operation, path)
|
||||
}
|
||||
|
||||
// Client returns the API client to a Vault server given the configured
|
||||
|
|
|
@ -426,7 +426,7 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
|||
}
|
||||
logicalBackends["cubbyhole"] = CubbyholeBackendFactory
|
||||
logicalBackends["system"] = func(config *logical.BackendConfig) (logical.Backend, error) {
|
||||
return NewSystemBackend(c, config), nil
|
||||
return NewSystemBackend(c, config)
|
||||
}
|
||||
c.logicalBackends = logicalBackends
|
||||
|
||||
|
@ -1516,3 +1516,27 @@ func (c *Core) BarrierKeyLength() (min, max int) {
|
|||
max += shamir.ShareOverhead
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Core) ValidateWrappingToken(token string) (bool, error) {
|
||||
if token == "" {
|
||||
return false, fmt.Errorf("token is empty")
|
||||
}
|
||||
|
||||
te, err := c.tokenStore.Lookup(token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if te == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(te.Policies) != 1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if te.Policies[0] != responseWrappingPolicyName {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -1914,7 +1914,10 @@ path "secret/*" {
|
|||
}
|
||||
|
||||
// Renew the lease
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "sys/renew/"+resp.Secret.LeaseID)
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "sys/renew")
|
||||
req.Data = map[string]interface{}{
|
||||
"lease_id": resp.Secret.LeaseID,
|
||||
}
|
||||
req.ClientToken = lresp.Auth.ClientToken
|
||||
_, err = c.HandleRequest(req)
|
||||
if err != nil {
|
||||
|
|
|
@ -3,6 +3,7 @@ package vault
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -22,7 +23,7 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend {
|
||||
func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := &SystemBackend{
|
||||
Core: core,
|
||||
}
|
||||
|
@ -540,12 +541,72 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend
|
|||
HelpSynopsis: strings.TrimSpace(sysHelp["rotate"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["rotate"][1]),
|
||||
},
|
||||
|
||||
&framework.Path{
|
||||
Pattern: "wrapping/wrap$",
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.handleWrappingWrap,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["wrap"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["wrap"][1]),
|
||||
},
|
||||
|
||||
&framework.Path{
|
||||
Pattern: "wrapping/unwrap$",
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"token": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.handleWrappingUnwrap,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["unwrap"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["unwrap"][1]),
|
||||
},
|
||||
|
||||
&framework.Path{
|
||||
Pattern: "wrapping/lookup$",
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"token": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.handleWrappingLookup,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["wraplookup"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["wraplookup"][1]),
|
||||
},
|
||||
|
||||
&framework.Path{
|
||||
Pattern: "wrapping/rewrap$",
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"token": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.handleWrappingRewrap,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["rewrap"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["rewrap"][1]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.Backend.Setup(config)
|
||||
|
||||
return b.Backend
|
||||
return b.Backend.Setup(config)
|
||||
}
|
||||
|
||||
// SystemBackend implements logical.Backend and is used to interact with
|
||||
|
@ -558,7 +619,11 @@ type SystemBackend struct {
|
|||
|
||||
// handleCapabilitiesreturns the ACL capabilities of the token for a given path
|
||||
func (b *SystemBackend) handleCapabilities(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
capabilities, err := b.Core.Capabilities(d.Get("token").(string), d.Get("path").(string))
|
||||
token := d.Get("token").(string)
|
||||
if token == "" {
|
||||
token = req.ClientToken
|
||||
}
|
||||
capabilities, err := b.Core.Capabilities(token, d.Get("path").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -660,6 +725,7 @@ func (b *SystemBackend) handleRekeyDelete(
|
|||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyDeleteBarrier(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.handleRekeyDelete(req, data, false)
|
||||
|
@ -1406,6 +1472,221 @@ func (b *SystemBackend) handleRotate(
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleWrappingWrap(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
if req.WrapTTL == 0 {
|
||||
return logical.ErrorResponse("endpoint requires response wrapping to be used"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: data.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleWrappingUnwrap(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// If a third party is unwrapping (rather than the calling token being the
|
||||
// wrapping token) we detect this so that we can revoke the original
|
||||
// wrapping token after reading it
|
||||
var thirdParty bool
|
||||
|
||||
token := data.Get("token").(string)
|
||||
if token != "" {
|
||||
thirdParty = true
|
||||
} else {
|
||||
token = req.ClientToken
|
||||
}
|
||||
|
||||
if thirdParty {
|
||||
// Use the token to decrement the use count to avoid a second operation on the token.
|
||||
_, err := b.Core.tokenStore.UseTokenByID(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decrementing wrapping token's use-count: %v", err)
|
||||
}
|
||||
|
||||
defer b.Core.tokenStore.Revoke(token)
|
||||
}
|
||||
|
||||
cubbyReq := &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "cubbyhole/response",
|
||||
ClientToken: token,
|
||||
}
|
||||
cubbyResp, err := b.Core.router.Route(cubbyReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up wrapping information: %v", err)
|
||||
}
|
||||
if cubbyResp == nil {
|
||||
return logical.ErrorResponse("no information found; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
if cubbyResp != nil && cubbyResp.IsError() {
|
||||
return cubbyResp, nil
|
||||
}
|
||||
if cubbyResp.Data == nil {
|
||||
return logical.ErrorResponse("wrapping information was nil; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
|
||||
responseRaw := cubbyResp.Data["response"]
|
||||
if responseRaw == nil {
|
||||
return nil, fmt.Errorf("no response found inside the cubbyhole")
|
||||
}
|
||||
response, ok := responseRaw.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not decode response inside the cubbyhole")
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
if len(response) == 0 {
|
||||
resp.Data[logical.HTTPStatusCode] = 204
|
||||
} else {
|
||||
resp.Data[logical.HTTPStatusCode] = 200
|
||||
resp.Data[logical.HTTPRawBody] = []byte(response)
|
||||
resp.Data[logical.HTTPContentType] = "application/json"
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleWrappingLookup(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
token := data.Get("token").(string)
|
||||
|
||||
if token == "" {
|
||||
return logical.ErrorResponse("missing \"token\" value in input"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
cubbyReq := &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "cubbyhole/wrapinfo",
|
||||
ClientToken: token,
|
||||
}
|
||||
cubbyResp, err := b.Core.router.Route(cubbyReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up wrapping information: %v", err)
|
||||
}
|
||||
if cubbyResp == nil {
|
||||
return logical.ErrorResponse("no information found; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
if cubbyResp != nil && cubbyResp.IsError() {
|
||||
return cubbyResp, nil
|
||||
}
|
||||
if cubbyResp.Data == nil {
|
||||
return logical.ErrorResponse("wrapping information was nil; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
|
||||
creationTTLRaw := cubbyResp.Data["creation_ttl"]
|
||||
creationTime := cubbyResp.Data["creation_time"]
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
if creationTTLRaw != nil {
|
||||
creationTTL, err := creationTTLRaw.(json.Number).Int64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading creation_ttl value from wrapping information: %v", err)
|
||||
}
|
||||
resp.Data["creation_ttl"] = time.Duration(creationTTL).Seconds()
|
||||
}
|
||||
if creationTime != nil {
|
||||
// This was JSON marshaled so it's already a string in RFC3339 format
|
||||
resp.Data["creation_time"] = cubbyResp.Data["creation_time"]
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleWrappingRewrap(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// If a third party is rewrapping (rather than the calling token being the
|
||||
// wrapping token) we detect this so that we can revoke the original
|
||||
// wrapping token after reading it. Right now wrapped tokens can't unwrap
|
||||
// themselves, but in case we change it, this will be ready to do the right
|
||||
// thing.
|
||||
var thirdParty bool
|
||||
|
||||
token := data.Get("token").(string)
|
||||
if token != "" {
|
||||
thirdParty = true
|
||||
} else {
|
||||
token = req.ClientToken
|
||||
}
|
||||
|
||||
if thirdParty {
|
||||
// Use the token to decrement the use count to avoid a second operation on the token.
|
||||
_, err := b.Core.tokenStore.UseTokenByID(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decrementing wrapping token's use-count: %v", err)
|
||||
}
|
||||
defer b.Core.tokenStore.Revoke(token)
|
||||
}
|
||||
|
||||
// Fetch the original TTL
|
||||
cubbyReq := &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "cubbyhole/wrapinfo",
|
||||
ClientToken: token,
|
||||
}
|
||||
cubbyResp, err := b.Core.router.Route(cubbyReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up wrapping information: %v", err)
|
||||
}
|
||||
if cubbyResp == nil {
|
||||
return logical.ErrorResponse("no information found; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
if cubbyResp != nil && cubbyResp.IsError() {
|
||||
return cubbyResp, nil
|
||||
}
|
||||
if cubbyResp.Data == nil {
|
||||
return logical.ErrorResponse("wrapping information was nil; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
|
||||
// Set the creation TTL on the request
|
||||
creationTTLRaw := cubbyResp.Data["creation_ttl"]
|
||||
if creationTTLRaw == nil {
|
||||
return nil, fmt.Errorf("creation_ttl value in wrapping information was nil")
|
||||
}
|
||||
creationTTL, err := cubbyResp.Data["creation_ttl"].(json.Number).Int64()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading creation_ttl value from wrapping information: %v", err)
|
||||
}
|
||||
|
||||
// Fetch the original response and return it as the data for the new response
|
||||
cubbyReq = &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "cubbyhole/response",
|
||||
ClientToken: token,
|
||||
}
|
||||
cubbyResp, err = b.Core.router.Route(cubbyReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up response: %v", err)
|
||||
}
|
||||
if cubbyResp == nil {
|
||||
return logical.ErrorResponse("no information found; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
if cubbyResp != nil && cubbyResp.IsError() {
|
||||
return cubbyResp, nil
|
||||
}
|
||||
if cubbyResp.Data == nil {
|
||||
return logical.ErrorResponse("wrapping information was nil; wrapping token may be from a previous Vault version"), nil
|
||||
}
|
||||
|
||||
response := cubbyResp.Data["response"]
|
||||
if response == nil {
|
||||
return nil, fmt.Errorf("no response found inside the cubbyhole")
|
||||
}
|
||||
|
||||
// Return response in "response"; wrapping code will detect the rewrap and
|
||||
// slot in instead of nesting
|
||||
req.WrapTTL = time.Duration(creationTTL)
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"response": response,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sanitizeMountPath(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
|
@ -1798,4 +2079,27 @@ Enable a new audit backend or disable an existing backend.
|
|||
`When there is no access to the token, token accessor can be used to fetch the token's capabilities
|
||||
on a given path.`,
|
||||
},
|
||||
|
||||
"wrap": {
|
||||
"Response-wraps an arbitrary JSON object.",
|
||||
`Round trips the given input data into a response-wrapped token.`,
|
||||
},
|
||||
|
||||
"unwrap": {
|
||||
"Unwraps a response-wrapped token.",
|
||||
`Unwraps a response-wrapped token. Unlike simply reading from cubbyhole/response,
|
||||
this provides additional validation on the token, and rather than a JSON-escaped
|
||||
string, the returned response is the exact same as the contained wrapped response.`,
|
||||
},
|
||||
|
||||
"wraplookup": {
|
||||
"Looks up the properties of a response-wrapped token.",
|
||||
`Returns the creation TTL and creation time of a response-wrapped token.`,
|
||||
},
|
||||
|
||||
"rewrap": {
|
||||
"Rotates a response-wrapped token.",
|
||||
`Rotates a response-wrapped token; the output is a new token with the same
|
||||
response wrapped inside and the same creation TTL. The original token is revoked.`,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -509,14 +509,18 @@ func TestSystemBackend_revokePrefixAuth(t *testing.T) {
|
|||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
b := NewSystemBackend(core, bc)
|
||||
b, err := NewSystemBackend(core, bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exp := ts.expiration
|
||||
|
||||
te := &TokenEntry{
|
||||
ID: "foo",
|
||||
Path: "auth/github/login/bar",
|
||||
}
|
||||
err := ts.create(te)
|
||||
err = ts.create(te)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -1038,7 +1042,13 @@ func testSystemBackend(t *testing.T) logical.Backend {
|
|||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
return NewSystemBackend(c, bc)
|
||||
|
||||
b, err := NewSystemBackend(c, bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
|
||||
|
@ -1050,5 +1060,10 @@ func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
|
|||
MaxLeaseTTLVal: time.Hour * 24 * 32,
|
||||
},
|
||||
}
|
||||
return c, NewSystemBackend(c, bc), root
|
||||
|
||||
b, err := NewSystemBackend(c, bc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c, b, root
|
||||
}
|
||||
|
|
|
@ -19,48 +19,75 @@ const (
|
|||
// policyCacheSize is the number of policies that are kept cached
|
||||
policyCacheSize = 1024
|
||||
|
||||
// cubbyholeResponseWrappingPolicyName is the name of the fixed policy
|
||||
cubbyholeResponseWrappingPolicyName = "response-wrapping"
|
||||
// responseWrappingPolicyName is the name of the fixed policy
|
||||
responseWrappingPolicyName = "response-wrapping"
|
||||
|
||||
// cubbyholeResponseWrappingPolicy is the policy that ensures cubbyhole
|
||||
// response wrapping can always succeed
|
||||
cubbyholeResponseWrappingPolicy = `
|
||||
// responseWrappingPolicy is the policy that ensures cubbyhole response
|
||||
// wrapping can always succeed. Note that sys/wrapping/lookup isn't
|
||||
// contained here because using it would revoke the token anyways, so there
|
||||
// isn't much point.
|
||||
responseWrappingPolicy = `
|
||||
path "cubbyhole/response" {
|
||||
capabilities = ["create", "read"]
|
||||
}
|
||||
|
||||
path "sys/wrapping/unwrap" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
`
|
||||
|
||||
// defaultPolicy is the "default" policy
|
||||
defaultPolicy = `
|
||||
# Allow tokens to look up their own properties
|
||||
path "auth/token/lookup-self" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# Allow tokens to renew themselves
|
||||
path "auth/token/renew-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow tokens to revoke themselves
|
||||
path "auth/token/revoke-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
path "cubbyhole/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
path "cubbyhole" {
|
||||
capabilities = ["list"]
|
||||
}
|
||||
|
||||
# Allow a token to look up its own capabilities on a path
|
||||
path "sys/capabilities-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow a token to renew a lease via lease_id in the request body
|
||||
path "sys/renew" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
path "sys/renew/*" {
|
||||
# Allow a token to manage its own cubbyhole
|
||||
path "cubbyhole/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
# Allow a token to list its cubbyhole (not covered by the splat above)
|
||||
path "cubbyhole" {
|
||||
capabilities = ["list"]
|
||||
}
|
||||
|
||||
# Allow a token to wrap arbitrary values in a response-wrapping token
|
||||
path "sys/wrapping/wrap" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow a token to look up the creation time and TTL of a given
|
||||
# response-wrapping token
|
||||
path "sys/wrapping/lookup" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow a token to unwrap a response-wrapping token. This is a convenience to
|
||||
# avoid client token swapping since this is also part of the response wrapping
|
||||
# policy.
|
||||
path "sys/wrapping/unwrap" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
`
|
||||
|
@ -69,10 +96,10 @@ path "sys/renew/*" {
|
|||
var (
|
||||
immutablePolicies = []string{
|
||||
"root",
|
||||
cubbyholeResponseWrappingPolicyName,
|
||||
responseWrappingPolicyName,
|
||||
}
|
||||
nonAssignablePolicies = []string{
|
||||
cubbyholeResponseWrappingPolicyName,
|
||||
responseWrappingPolicyName,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -125,12 +152,12 @@ func (c *Core) setupPolicyStore() error {
|
|||
}
|
||||
|
||||
// Ensure that the cubbyhole response wrapping policy exists
|
||||
policy, err = c.policyStore.GetPolicy(cubbyholeResponseWrappingPolicyName)
|
||||
policy, err = c.policyStore.GetPolicy(responseWrappingPolicyName)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("error fetching response-wrapping policy from store: {{err}}", err)
|
||||
}
|
||||
if policy == nil || policy.Raw != cubbyholeResponseWrappingPolicy {
|
||||
err := c.policyStore.createCubbyholeResponseWrappingPolicy()
|
||||
if policy == nil || policy.Raw != responseWrappingPolicy {
|
||||
err := c.policyStore.createResponseWrappingPolicy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -324,16 +351,16 @@ func (ps *PolicyStore) createDefaultPolicy() error {
|
|||
return ps.setPolicyInternal(policy)
|
||||
}
|
||||
|
||||
func (ps *PolicyStore) createCubbyholeResponseWrappingPolicy() error {
|
||||
policy, err := Parse(cubbyholeResponseWrappingPolicy)
|
||||
func (ps *PolicyStore) createResponseWrappingPolicy() error {
|
||||
policy, err := Parse(responseWrappingPolicy)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(fmt.Sprintf("error parsing %s policy: {{err}}", cubbyholeResponseWrappingPolicyName), err)
|
||||
return errwrap.Wrapf(fmt.Sprintf("error parsing %s policy: {{err}}", responseWrappingPolicyName), err)
|
||||
}
|
||||
|
||||
if policy == nil {
|
||||
return fmt.Errorf("parsing %s policy resulted in nil policy", cubbyholeResponseWrappingPolicyName)
|
||||
return fmt.Errorf("parsing %s policy resulted in nil policy", responseWrappingPolicyName)
|
||||
}
|
||||
|
||||
policy.Name = cubbyholeResponseWrappingPolicyName
|
||||
policy.Name = responseWrappingPolicyName
|
||||
return ps.setPolicyInternal(policy)
|
||||
}
|
||||
|
|
|
@ -147,8 +147,8 @@ func TestPolicyStore_Predefined(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if pCubby.Raw != cubbyholeResponseWrappingPolicy {
|
||||
t.Fatalf("bad: expected\n%s\ngot\n%s\n", cubbyholeResponseWrappingPolicy, pCubby.Raw)
|
||||
if pCubby.Raw != responseWrappingPolicy {
|
||||
t.Fatalf("bad: expected\n%s\ngot\n%s\n", responseWrappingPolicy, pCubby.Raw)
|
||||
}
|
||||
pRoot, err := core.policyStore.GetPolicy("root")
|
||||
if err != nil {
|
||||
|
|
|
@ -2,6 +2,7 @@ package vault
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -53,15 +54,30 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
|
|||
}
|
||||
|
||||
// We are wrapping if there is anything to wrap (not a nil response) and a
|
||||
// TTL was specified for the token
|
||||
wrapping := resp != nil && resp.WrapInfo != nil && resp.WrapInfo.TTL != 0
|
||||
// TTL was specified for the token. Errors on a call should be returned to
|
||||
// the caller, so wrapping is turned off if an error is hit and the error
|
||||
// is logged to the audit log.
|
||||
wrapping := resp != nil &&
|
||||
err == nil &&
|
||||
!resp.IsError() &&
|
||||
resp.WrapInfo != nil &&
|
||||
resp.WrapInfo.TTL != 0
|
||||
|
||||
if wrapping {
|
||||
cubbyResp, err := c.wrapInCubbyhole(req, resp)
|
||||
cubbyResp, cubbyErr := c.wrapInCubbyhole(req, resp)
|
||||
// If not successful, returns either an error response from the
|
||||
// cubbyhole backend or an error; if either is set, return
|
||||
if cubbyResp != nil || err != nil {
|
||||
return cubbyResp, err
|
||||
// cubbyhole backend or an error; if either is set, set resp and err to
|
||||
// those and continue so that that's what we audit log. Otherwise
|
||||
// finish the wrapping and audit log that.
|
||||
if cubbyResp != nil || cubbyErr != nil {
|
||||
resp = cubbyResp
|
||||
err = cubbyErr
|
||||
} else {
|
||||
wrappingResp := &logical.Response{
|
||||
WrapInfo: resp.WrapInfo,
|
||||
}
|
||||
wrappingResp.CloneWarnings(resp)
|
||||
resp = wrappingResp
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,16 +87,6 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
|
|||
return nil, ErrInternalError
|
||||
}
|
||||
|
||||
// If we are wrapping, now is when we create a new response object with the
|
||||
// wrapped information, since the original response has been audit logged
|
||||
if wrapping {
|
||||
wrappingResp := &logical.Response{
|
||||
WrapInfo: resp.WrapInfo,
|
||||
}
|
||||
wrappingResp.CloneWarnings(resp)
|
||||
resp = wrappingResp
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -244,6 +250,13 @@ func (c *Core) handleRequest(req *logical.Request) (retResp *logical.Response, r
|
|||
}
|
||||
}
|
||||
|
||||
if resp != nil &&
|
||||
req.Path == "cubbyhole/response" &&
|
||||
len(te.Policies) == 1 &&
|
||||
te.Policies[0] == responseWrappingPolicyName {
|
||||
resp.AddWarning("Please use sys/wrapping/unwrap to unwrap responses, as it provides additional security checks.")
|
||||
}
|
||||
|
||||
// Return the response and error
|
||||
if err != nil {
|
||||
retErr = multierror.Append(retErr, err)
|
||||
|
@ -333,6 +346,13 @@ func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, *log
|
|||
|
||||
te.Policies = policyutil.SanitizePolicies(te.Policies, true)
|
||||
|
||||
// Prevent internal policies from being assigned to tokens
|
||||
for _, policy := range te.Policies {
|
||||
if strutil.StrListContains(nonAssignablePolicies, policy) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), nil, logical.ErrInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.tokenStore.create(&te); err != nil {
|
||||
c.logger.Error("core: failed to create token", "error", err)
|
||||
return nil, auth, ErrInternalError
|
||||
|
@ -404,31 +424,39 @@ func (c *Core) wrapInCubbyhole(req *logical.Request, resp *logical.Response) (*l
|
|||
resp.WrapInfo.WrappedAccessor = resp.Auth.Accessor
|
||||
}
|
||||
|
||||
httpResponse := logical.SanitizeResponse(resp)
|
||||
|
||||
// Add the unique identifier of the original request to the response
|
||||
httpResponse.RequestID = req.ID
|
||||
|
||||
// Because of the way that JSON encodes (likely just in Go) we actually get
|
||||
// mixed-up values for ints if we simply put this object in the response
|
||||
// and encode the whole thing; so instead we marshal it first, then store
|
||||
// the string response. This actually ends up making it easier on the
|
||||
// client side, too, as it becomes a straight read-string-pass-to-unmarshal
|
||||
// operation.
|
||||
|
||||
marshaledResponse, err := json.Marshal(httpResponse)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to marshal wrapped response", "error", err)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
|
||||
cubbyReq := &logical.Request{
|
||||
Operation: logical.CreateOperation,
|
||||
Path: "cubbyhole/response",
|
||||
ClientToken: te.ID,
|
||||
Data: map[string]interface{}{
|
||||
}
|
||||
|
||||
// During a rewrap, store the original response, don't wrap it again.
|
||||
if req.Path == "sys/wrapping/rewrap" {
|
||||
cubbyReq.Data = map[string]interface{}{
|
||||
"response": resp.Data["response"],
|
||||
}
|
||||
} else {
|
||||
httpResponse := logical.SanitizeResponse(resp)
|
||||
|
||||
// Add the unique identifier of the original request to the response
|
||||
httpResponse.RequestID = req.ID
|
||||
|
||||
// Because of the way that JSON encodes (likely just in Go) we actually get
|
||||
// mixed-up values for ints if we simply put this object in the response
|
||||
// and encode the whole thing; so instead we marshal it first, then store
|
||||
// the string response. This actually ends up making it easier on the
|
||||
// client side, too, as it becomes a straight read-string-pass-to-unmarshal
|
||||
// operation.
|
||||
|
||||
marshaledResponse, err := json.Marshal(httpResponse)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to marshal wrapped response", "error", err)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
|
||||
cubbyReq.Data = map[string]interface{}{
|
||||
"response": string(marshaledResponse),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
cubbyResp, err := c.router.Route(cubbyReq)
|
||||
|
@ -444,6 +472,25 @@ func (c *Core) wrapInCubbyhole(req *logical.Request, resp *logical.Response) (*l
|
|||
return cubbyResp, nil
|
||||
}
|
||||
|
||||
// Store info for lookup
|
||||
cubbyReq.Path = "cubbyhole/wrapinfo"
|
||||
cubbyReq.Data = map[string]interface{}{
|
||||
"creation_ttl": resp.WrapInfo.TTL,
|
||||
"creation_time": creationTime,
|
||||
}
|
||||
cubbyResp, err = c.router.Route(cubbyReq)
|
||||
if err != nil {
|
||||
// Revoke since it's not yet being tracked for expiration
|
||||
c.tokenStore.Revoke(te.ID)
|
||||
c.logger.Error("core: failed to store wrapping information", "error", err)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
if cubbyResp != nil && cubbyResp.IsError() {
|
||||
c.tokenStore.Revoke(te.ID)
|
||||
c.logger.Error("core: failed to store wrapping information", "error", cubbyResp.Data["error"])
|
||||
return cubbyResp, nil
|
||||
}
|
||||
|
||||
auth := &logical.Auth{
|
||||
ClientToken: te.ID,
|
||||
Policies: []string{"response-wrapping"},
|
||||
|
|
|
@ -223,7 +223,7 @@ func (r *Router) routeCommon(req *logical.Request, existenceCheck bool) (*logica
|
|||
}
|
||||
|
||||
// Adjust the path to exclude the routing prefix
|
||||
original := req.Path
|
||||
originalPath := req.Path
|
||||
req.Path = strings.TrimPrefix(req.Path, mount)
|
||||
req.MountPoint = mount
|
||||
if req.Path == "/" {
|
||||
|
@ -236,8 +236,9 @@ func (r *Router) routeCommon(req *logical.Request, existenceCheck bool) (*logica
|
|||
// Hash the request token unless this is the token backend
|
||||
clientToken := req.ClientToken
|
||||
switch {
|
||||
case strings.HasPrefix(original, "auth/token/"):
|
||||
case strings.HasPrefix(original, "cubbyhole/"):
|
||||
case strings.HasPrefix(originalPath, "auth/token/"):
|
||||
case strings.HasPrefix(originalPath, "sys/"):
|
||||
case strings.HasPrefix(originalPath, "cubbyhole/"):
|
||||
// In order for the token store to revoke later, we need to have the same
|
||||
// salted ID, so we double-salt what's going to the cubbyhole backend
|
||||
req.ClientToken = re.SaltID(r.tokenStoreSalt.SaltID(req.ClientToken))
|
||||
|
@ -251,14 +252,23 @@ func (r *Router) routeCommon(req *logical.Request, existenceCheck bool) (*logica
|
|||
// Cache the identifier of the request
|
||||
originalReqID := req.ID
|
||||
|
||||
// Cache the wrap TTL of the request
|
||||
originalWrapTTL := req.WrapTTL
|
||||
|
||||
// Reset the request before returning
|
||||
defer func() {
|
||||
req.Path = original
|
||||
req.Path = originalPath
|
||||
req.MountPoint = ""
|
||||
req.Connection = originalConn
|
||||
req.ID = originalReqID
|
||||
req.Storage = nil
|
||||
req.ClientToken = clientToken
|
||||
|
||||
// Only the rewrap endpoint is allowed to declare a wrap TTL on a
|
||||
// request that did not come from the client
|
||||
if req.Path != "sys/wrapping/rewrap" {
|
||||
req.WrapTTL = originalWrapTTL
|
||||
}
|
||||
}()
|
||||
|
||||
// Invoke the backend
|
||||
|
|
|
@ -591,8 +591,6 @@ func TestCluster(t *testing.T, handlers []http.Handler, base *CoreConfig, unseal
|
|||
DisableMlock: true,
|
||||
}
|
||||
|
||||
coreConfig.LogicalBackends["generic"] = PassthroughBackendFactory
|
||||
|
||||
if base != nil {
|
||||
// Used to set something non-working to test fallback
|
||||
switch base.ClusterAddr {
|
||||
|
|
|
@ -763,6 +763,15 @@ func (ts *TokenStore) UseToken(te *TokenEntry) (*TokenEntry, error) {
|
|||
return te, nil
|
||||
}
|
||||
|
||||
func (ts *TokenStore) UseTokenByID(id string) (*TokenEntry, error) {
|
||||
te, err := ts.Lookup(id)
|
||||
if err != nil {
|
||||
return te, err
|
||||
}
|
||||
|
||||
return ts.UseToken(te)
|
||||
}
|
||||
|
||||
// Lookup is used to find a token given its ID. It acquires a read lock, then calls lookupSalted.
|
||||
func (ts *TokenStore) Lookup(id string) (*TokenEntry, error) {
|
||||
defer metrics.MeasureSince([]string{"token", "lookup"}, time.Now())
|
||||
|
@ -1259,7 +1268,7 @@ func (ts *TokenStore) handleCreateCommon(
|
|||
// Prevent internal policies from being assigned to tokens
|
||||
for _, policy := range te.Policies {
|
||||
if strutil.StrListContains(nonAssignablePolicies, policy) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("cannot assign %s policy", policy)), nil
|
||||
return logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -766,7 +766,7 @@ func TestTokenStore_HandleRequest_NonAssignable(t *testing.T) {
|
|||
t.Fatalf("err: %v %v", err, resp)
|
||||
}
|
||||
|
||||
req.Data["policies"] = []string{"default", "foo", cubbyholeResponseWrappingPolicyName}
|
||||
req.Data["policies"] = []string{"default", "foo", responseWrappingPolicyName}
|
||||
|
||||
resp, err = ts.HandleRequest(req)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/wrapping/lookup"
|
||||
sidebar_current: "docs-http-wrapping-lookup"
|
||||
description: |-
|
||||
The '/sys/wrapping/lookup' endpoint returns wrapping token properties
|
||||
---
|
||||
|
||||
# /sys/wrapping/lookup
|
||||
|
||||
## POST
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Looks up wrapping properties for the given token.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/wrapping/lookup`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">token</span>
|
||||
<span class="param-flags">required</span>
|
||||
The wrapping token ID.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"request_id": "481320f5-fdf8-885d-8050-65fa767fd19b",
|
||||
"lease_id": "",
|
||||
"lease_duration": 0,
|
||||
"renewable": false,
|
||||
"data": {
|
||||
"creation_time": "2016-09-28T14:16:13.07103516-04:00",
|
||||
"creation_ttl": 300
|
||||
},
|
||||
"warnings": null
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/wrapping/rewrap"
|
||||
sidebar_current: "docs-http-wrapping-rewrap"
|
||||
description: |-
|
||||
The '/sys/wrapping/rewrap' endpoint can be used to rotate a wrapping token and refresh its TTL
|
||||
---
|
||||
|
||||
# /sys/wrapping/rewrap
|
||||
|
||||
## POST
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Rewraps a response-wrapped token; the new token will use the same creation
|
||||
TTL as the original token and contain the same response. The old token will
|
||||
be invalidated. This can be used for long-term storage of a secret in a
|
||||
response-wrapped token when rotation is a requirement.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/wrapping/rewrap`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">token</span>
|
||||
<span class="param-flags">required</span>
|
||||
The wrapping token ID.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"request_id": "",
|
||||
"lease_id": "",
|
||||
"lease_duration": 0,
|
||||
"renewable": false,
|
||||
"data": null,
|
||||
"warnings": null,
|
||||
"wrap_info": {
|
||||
"token": "3b6f1193-0707-ac17-284d-e41032e74d1f",
|
||||
"ttl": 300,
|
||||
"creation_time": "2016-09-28T14:22:26.486186607-04:00",
|
||||
"wrapped_accessor": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/wrapping/unwrap"
|
||||
sidebar_current: "docs-http-wrapping-unwrap"
|
||||
description: |-
|
||||
The '/sys/wrapping/unwrap' endpoint unwraps a wrapped response
|
||||
---
|
||||
|
||||
# /sys/wrapping/unwrap
|
||||
|
||||
## POST
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Returns the original response inside the given wrapping token. Unlike
|
||||
simply reading `cubbyhole/response`, this endpoint provides additional
|
||||
validation checks on the token, and returns the original value on the wire
|
||||
rather than a JSON string representation of it.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/wrapping/unwrap`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">token</span>
|
||||
<span class="param-flags">required</span>
|
||||
The wrapping token ID.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"request_id": "8e33c808-f86c-cff8-f30a-fbb3ac22c4a8",
|
||||
"lease_id": "",
|
||||
"lease_duration": 2592000,
|
||||
"renewable": false,
|
||||
"data": {
|
||||
"zip": "zap"
|
||||
},
|
||||
"warnings": null
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/wrapping/wrap"
|
||||
sidebar_current: "docs-http-wrapping-wrap"
|
||||
description: |-
|
||||
The '/sys/wrapping/wrap' endpoint wraps the given values in a response-wrapped token
|
||||
---
|
||||
|
||||
# /sys/wrapping/wrap
|
||||
|
||||
## POST
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Wraps the given user-supplied data inside a response-wrapped token.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/sys/wrapping/wrap`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">[any]</span>
|
||||
<span class="param-flags">optional</span>
|
||||
Parameters should be supplied as keys/values in a JSON object. The
|
||||
exact set of given parameters will be contained in the wrapped
|
||||
response.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```javascript
|
||||
{
|
||||
"request_id": "",
|
||||
"lease_id": "",
|
||||
"lease_duration": 0,
|
||||
"renewable": false,
|
||||
"data": null,
|
||||
"warnings": null,
|
||||
"wrap_info": {
|
||||
"token": "fb79b9d3-d94e-9eb6-4919-c559311133d6",
|
||||
"ttl": 300,
|
||||
"creation_time": "2016-09-28T14:41:00.56961496-04:00",
|
||||
"wrapped_accessor": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
|
@ -117,6 +117,24 @@
|
|||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-http-wrapping") %>>
|
||||
<a href="#">Response Wrapping</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-http-wrapping-lookup") %>>
|
||||
<a href="/docs/http/sys-wrapping-lookup.html">/sys/wrapping/lookup</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-wrapping-rewrap") %>>
|
||||
<a href="/docs/http/sys-wrapping-rewrap.html">/sys/wrapping/rewrap</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-wrapping-unwrap") %>>
|
||||
<a href="/docs/http/sys-wrapping-unwrap.html">/sys/wrapping/unwrap</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-wrapping-wrap") %>>
|
||||
<a href="/docs/http/sys-wrapping-wrap.html">/sys/wrapping/wrap</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-http-ha") %>>
|
||||
<a href="#">High Availability</a>
|
||||
<ul class="nav nav-visible">
|
||||
|
|
Loading…
Reference in New Issue