Wrapping enhancements (#1927)

This commit is contained in:
Jeff Mitchell 2016-09-28 21:01:28 -07:00 committed by GitHub
parent c474af7d24
commit b45a481365
25 changed files with 1277 additions and 143 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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{

View File

@ -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
}

View File

@ -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:

View File

@ -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
}

View File

@ -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)
}

350
http/sys_wrapping_test.go Normal file
View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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.`,
},
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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"},

View File

@ -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

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">