2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2015-03-15 21:26:48 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2018-01-08 18:31:38 +00:00
|
|
|
"context"
|
2015-03-15 21:26:48 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2015-03-16 00:07:54 +00:00
|
|
|
"strings"
|
2015-03-15 21:26:48 +00:00
|
|
|
|
2021-07-16 00:17:31 +00:00
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
2019-04-13 07:44:06 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/wrapping"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2015-03-15 21:26:48 +00:00
|
|
|
)
|
|
|
|
|
2015-09-19 22:24:53 +00:00
|
|
|
// PassthroughBackendFactory returns a PassthroughBackend
|
|
|
|
// with leases switched off
|
2018-01-19 06:44:44 +00:00
|
|
|
func PassthroughBackendFactory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
2022-12-15 18:09:36 +00:00
|
|
|
return LeaseSwitchedPassthroughBackend(ctx, conf, nil)
|
2015-09-19 22:24:53 +00:00
|
|
|
}
|
|
|
|
|
Backend plugin system (#2874)
* Add backend plugin changes
* Fix totp backend plugin tests
* Fix logical/plugin InvalidateKey test
* Fix plugin catalog CRUD test, fix NoopBackend
* Clean up commented code block
* Fix system backend mount test
* Set plugin_name to omitempty, fix handleMountTable config parsing
* Clean up comments, keep shim connections alive until cleanup
* Include pluginClient, disallow LookupPlugin call from within a plugin
* Add wrapper around backendPluginClient for proper cleanup
* Add logger shim tests
* Add logger, storage, and system shim tests
* Use pointer receivers for system view shim
* Use plugin name if no path is provided on mount
* Enable plugins for auth backends
* Add backend type attribute, move builtin/plugin/package
* Fix merge conflict
* Fix missing plugin name in mount config
* Add integration tests on enabling auth backend plugins
* Remove dependency cycle on mock-plugin
* Add passthrough backend plugin, use logical.BackendType to determine lease generation
* Remove vault package dependency on passthrough package
* Add basic impl test for passthrough plugin
* Incorporate feedback; set b.backend after shims creation on backendPluginServer
* Fix totp plugin test
* Add plugin backends docs
* Fix tests
* Fix builtin/plugin tests
* Remove flatten from PluginRunner fields
* Move mock plugin to logical/plugin, remove totp and passthrough plugins
* Move pluginMap into newPluginClient
* Do not create storage RPC connection on HandleRequest and HandleExistenceCheck
* Change shim logger's Fatal to no-op
* Change BackendType to uint32, match UX backend types
* Change framework.Backend Setup signature
* Add Setup func to logical.Backend interface
* Move OptionallyEnableMlock call into plugin.Serve, update docs and comments
* Remove commented var in plugin package
* RegisterLicense on logical.Backend interface (#3017)
* Add RegisterLicense to logical.Backend interface
* Update RegisterLicense to use callback func on framework.Backend
* Refactor framework.Backend.RegisterLicense
* plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs
* plugin: Revert BackendType to remove TypePassthrough and related references
* Fix typo in plugin backends docs
2017-07-20 17:28:40 +00:00
|
|
|
// LeasedPassthroughBackendFactory returns a PassthroughBackend
|
2015-09-19 22:24:53 +00:00
|
|
|
// with leases switched on
|
2018-01-19 06:44:44 +00:00
|
|
|
func LeasedPassthroughBackendFactory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
2022-12-15 18:09:36 +00:00
|
|
|
return LeaseSwitchedPassthroughBackend(ctx, conf, func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
return nil, nil
|
|
|
|
})
|
2015-09-19 22:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-12-15 18:09:36 +00:00
|
|
|
type revokeFunc func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error)
|
|
|
|
|
Backend plugin system (#2874)
* Add backend plugin changes
* Fix totp backend plugin tests
* Fix logical/plugin InvalidateKey test
* Fix plugin catalog CRUD test, fix NoopBackend
* Clean up commented code block
* Fix system backend mount test
* Set plugin_name to omitempty, fix handleMountTable config parsing
* Clean up comments, keep shim connections alive until cleanup
* Include pluginClient, disallow LookupPlugin call from within a plugin
* Add wrapper around backendPluginClient for proper cleanup
* Add logger shim tests
* Add logger, storage, and system shim tests
* Use pointer receivers for system view shim
* Use plugin name if no path is provided on mount
* Enable plugins for auth backends
* Add backend type attribute, move builtin/plugin/package
* Fix merge conflict
* Fix missing plugin name in mount config
* Add integration tests on enabling auth backend plugins
* Remove dependency cycle on mock-plugin
* Add passthrough backend plugin, use logical.BackendType to determine lease generation
* Remove vault package dependency on passthrough package
* Add basic impl test for passthrough plugin
* Incorporate feedback; set b.backend after shims creation on backendPluginServer
* Fix totp plugin test
* Add plugin backends docs
* Fix tests
* Fix builtin/plugin tests
* Remove flatten from PluginRunner fields
* Move mock plugin to logical/plugin, remove totp and passthrough plugins
* Move pluginMap into newPluginClient
* Do not create storage RPC connection on HandleRequest and HandleExistenceCheck
* Change shim logger's Fatal to no-op
* Change BackendType to uint32, match UX backend types
* Change framework.Backend Setup signature
* Add Setup func to logical.Backend interface
* Move OptionallyEnableMlock call into plugin.Serve, update docs and comments
* Remove commented var in plugin package
* RegisterLicense on logical.Backend interface (#3017)
* Add RegisterLicense to logical.Backend interface
* Update RegisterLicense to use callback func on framework.Backend
* Refactor framework.Backend.RegisterLicense
* plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs
* plugin: Revert BackendType to remove TypePassthrough and related references
* Fix typo in plugin backends docs
2017-07-20 17:28:40 +00:00
|
|
|
// LeaseSwitchedPassthroughBackend returns a PassthroughBackend
|
2015-09-19 22:24:53 +00:00
|
|
|
// with leases switched on or off
|
2022-12-15 18:09:36 +00:00
|
|
|
func LeaseSwitchedPassthroughBackend(ctx context.Context, conf *logical.BackendConfig, revoke revokeFunc) (logical.Backend, error) {
|
2015-03-16 00:07:54 +00:00
|
|
|
var b PassthroughBackend
|
2022-12-15 18:09:36 +00:00
|
|
|
if revoke == nil {
|
|
|
|
// We probably don't need this, since we should never have to handle revoke requests, but just in case...
|
|
|
|
b.revoke = func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
b.generateLeases = true
|
|
|
|
b.revoke = revoke
|
|
|
|
}
|
2015-03-19 22:11:42 +00:00
|
|
|
b.Backend = &framework.Backend{
|
2015-04-04 03:45:00 +00:00
|
|
|
Help: strings.TrimSpace(passthroughHelp),
|
|
|
|
|
2017-10-23 19:35:28 +00:00
|
|
|
PathsSpecial: &logical.Paths{
|
|
|
|
SealWrapStorage: []string{
|
2018-03-18 18:25:15 +00:00
|
|
|
"*",
|
2017-10-23 19:35:28 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
Paths: []*framework.Path{
|
2018-09-18 03:03:00 +00:00
|
|
|
{
|
2015-03-16 00:07:54 +00:00
|
|
|
Pattern: ".*",
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
|
|
logical.ReadOperation: b.handleRead,
|
2016-01-07 20:10:05 +00:00
|
|
|
logical.CreateOperation: b.handleWrite,
|
|
|
|
logical.UpdateOperation: b.handleWrite,
|
2015-03-16 00:07:54 +00:00
|
|
|
logical.DeleteOperation: b.handleDelete,
|
|
|
|
logical.ListOperation: b.handleList,
|
|
|
|
},
|
|
|
|
|
2016-01-07 20:10:05 +00:00
|
|
|
ExistenceCheck: b.handleExistenceCheck,
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
HelpSynopsis: strings.TrimSpace(passthroughHelpSynopsis),
|
|
|
|
HelpDescription: strings.TrimSpace(passthroughHelpDescription),
|
|
|
|
},
|
|
|
|
},
|
2018-11-07 01:21:24 +00:00
|
|
|
BackendType: logical.TypeLogical,
|
2015-09-19 22:24:53 +00:00
|
|
|
}
|
2015-03-19 19:20:25 +00:00
|
|
|
|
2015-10-07 15:42:23 +00:00
|
|
|
b.Backend.Secrets = []*framework.Secret{
|
2021-04-08 16:43:39 +00:00
|
|
|
{
|
2017-09-15 13:02:29 +00:00
|
|
|
Type: "kv",
|
2015-03-19 19:20:25 +00:00
|
|
|
|
2015-10-07 15:42:23 +00:00
|
|
|
Renew: b.handleRead,
|
2022-12-15 18:09:36 +00:00
|
|
|
Revoke: b.revoke,
|
2015-10-07 15:42:23 +00:00
|
|
|
},
|
2015-03-19 22:11:42 +00:00
|
|
|
}
|
|
|
|
|
2015-09-04 20:58:12 +00:00
|
|
|
if conf == nil {
|
2018-04-05 15:49:21 +00:00
|
|
|
return nil, fmt.Errorf("configuration passed into backend is nil")
|
2015-09-04 20:58:12 +00:00
|
|
|
}
|
2018-01-19 06:44:44 +00:00
|
|
|
b.Backend.Setup(ctx, conf)
|
2015-09-04 20:58:12 +00:00
|
|
|
|
2015-09-19 22:24:53 +00:00
|
|
|
return &b, nil
|
2015-03-15 21:53:41 +00:00
|
|
|
}
|
|
|
|
|
2015-03-15 21:26:48 +00:00
|
|
|
// PassthroughBackend is used storing secrets directly into the physical
|
2015-09-09 19:42:29 +00:00
|
|
|
// backend. The secrets are encrypted in the durable storage and custom TTL
|
2015-03-15 21:26:48 +00:00
|
|
|
// information can be specified, but otherwise this backend doesn't do anything
|
|
|
|
// fancy.
|
2015-03-19 22:11:42 +00:00
|
|
|
type PassthroughBackend struct {
|
|
|
|
*framework.Backend
|
2017-06-21 15:19:38 +00:00
|
|
|
generateLeases bool
|
2022-12-15 18:09:36 +00:00
|
|
|
revoke func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error)
|
2015-03-19 22:11:42 +00:00
|
|
|
}
|
2015-03-15 21:26:48 +00:00
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *PassthroughBackend) handleRevoke(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-16 23:26:34 +00:00
|
|
|
// This is a no-op
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *PassthroughBackend) handleExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
|
2018-01-19 06:44:44 +00:00
|
|
|
out, err := req.Storage.Get(ctx, req.Path)
|
2016-01-07 20:10:05 +00:00
|
|
|
if err != nil {
|
2021-05-11 17:12:54 +00:00
|
|
|
return false, fmt.Errorf("existence check failed: %w", err)
|
2016-01-07 20:10:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return out != nil, nil
|
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *PassthroughBackend) handleRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-15 21:26:48 +00:00
|
|
|
// Read the path
|
2018-01-19 06:44:44 +00:00
|
|
|
out, err := req.Storage.Get(ctx, req.Path)
|
2015-03-15 21:26:48 +00:00
|
|
|
if err != nil {
|
2021-05-11 17:12:54 +00:00
|
|
|
return nil, fmt.Errorf("read failed: %w", err)
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fast-path the no data case
|
|
|
|
if out == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the data
|
2015-03-19 14:05:22 +00:00
|
|
|
var rawData map[string]interface{}
|
2016-07-06 16:25:40 +00:00
|
|
|
|
|
|
|
if err := jsonutil.DecodeJSON(out.Value, &rawData); err != nil {
|
2021-05-11 17:12:54 +00:00
|
|
|
return nil, fmt.Errorf("json decoding failed: %w", err)
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
|
|
|
|
2015-09-19 22:24:53 +00:00
|
|
|
var resp *logical.Response
|
|
|
|
if b.generateLeases {
|
|
|
|
// Generate the response
|
2017-09-15 13:02:29 +00:00
|
|
|
resp = b.Secret("kv").Response(rawData, nil)
|
2017-06-21 15:19:38 +00:00
|
|
|
resp.Secret.Renewable = false
|
2015-09-19 22:24:53 +00:00
|
|
|
} else {
|
|
|
|
resp = &logical.Response{
|
|
|
|
Secret: &logical.Secret{},
|
|
|
|
Data: rawData,
|
|
|
|
}
|
|
|
|
}
|
2015-03-19 19:20:25 +00:00
|
|
|
|
2017-11-10 17:56:31 +00:00
|
|
|
// Ensure seal wrapping is carried through if the response is
|
|
|
|
// response-wrapped
|
|
|
|
if out.SealWrap {
|
|
|
|
if resp.WrapInfo == nil {
|
|
|
|
resp.WrapInfo = &wrapping.ResponseWrapInfo{}
|
|
|
|
}
|
|
|
|
resp.WrapInfo.SealWrap = out.SealWrap
|
|
|
|
}
|
|
|
|
|
2015-08-21 05:27:01 +00:00
|
|
|
// Check if there is a ttl key
|
2015-09-21 20:57:41 +00:00
|
|
|
ttlDuration := b.System().DefaultLeaseTTL()
|
2017-05-09 18:05:00 +00:00
|
|
|
ttlRaw, ok := rawData["ttl"]
|
|
|
|
if !ok {
|
|
|
|
ttlRaw, ok = rawData["lease"]
|
|
|
|
}
|
|
|
|
if ok {
|
|
|
|
dur, err := parseutil.ParseDurationSecond(ttlRaw)
|
2016-07-11 18:19:35 +00:00
|
|
|
if err == nil {
|
|
|
|
ttlDuration = dur
|
2015-09-19 22:24:53 +00:00
|
|
|
}
|
2016-06-09 00:14:36 +00:00
|
|
|
|
2015-09-19 22:24:53 +00:00
|
|
|
if b.generateLeases {
|
2017-06-21 15:19:38 +00:00
|
|
|
resp.Secret.Renewable = true
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-19 22:24:53 +00:00
|
|
|
resp.Secret.TTL = ttlDuration
|
|
|
|
|
2015-03-15 21:26:48 +00:00
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
Backend plugin system (#2874)
* Add backend plugin changes
* Fix totp backend plugin tests
* Fix logical/plugin InvalidateKey test
* Fix plugin catalog CRUD test, fix NoopBackend
* Clean up commented code block
* Fix system backend mount test
* Set plugin_name to omitempty, fix handleMountTable config parsing
* Clean up comments, keep shim connections alive until cleanup
* Include pluginClient, disallow LookupPlugin call from within a plugin
* Add wrapper around backendPluginClient for proper cleanup
* Add logger shim tests
* Add logger, storage, and system shim tests
* Use pointer receivers for system view shim
* Use plugin name if no path is provided on mount
* Enable plugins for auth backends
* Add backend type attribute, move builtin/plugin/package
* Fix merge conflict
* Fix missing plugin name in mount config
* Add integration tests on enabling auth backend plugins
* Remove dependency cycle on mock-plugin
* Add passthrough backend plugin, use logical.BackendType to determine lease generation
* Remove vault package dependency on passthrough package
* Add basic impl test for passthrough plugin
* Incorporate feedback; set b.backend after shims creation on backendPluginServer
* Fix totp plugin test
* Add plugin backends docs
* Fix tests
* Fix builtin/plugin tests
* Remove flatten from PluginRunner fields
* Move mock plugin to logical/plugin, remove totp and passthrough plugins
* Move pluginMap into newPluginClient
* Do not create storage RPC connection on HandleRequest and HandleExistenceCheck
* Change shim logger's Fatal to no-op
* Change BackendType to uint32, match UX backend types
* Change framework.Backend Setup signature
* Add Setup func to logical.Backend interface
* Move OptionallyEnableMlock call into plugin.Serve, update docs and comments
* Remove commented var in plugin package
* RegisterLicense on logical.Backend interface (#3017)
* Add RegisterLicense to logical.Backend interface
* Update RegisterLicense to use callback func on framework.Backend
* Refactor framework.Backend.RegisterLicense
* plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs
* plugin: Revert BackendType to remove TypePassthrough and related references
* Fix typo in plugin backends docs
2017-07-20 17:28:40 +00:00
|
|
|
func (b *PassthroughBackend) GeneratesLeases() bool {
|
|
|
|
return b.generateLeases
|
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *PassthroughBackend) handleWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2018-11-06 19:08:55 +00:00
|
|
|
if req.Path == "" {
|
|
|
|
return logical.ErrorResponse("missing path"), nil
|
|
|
|
}
|
|
|
|
|
2015-03-15 21:26:48 +00:00
|
|
|
// Check that some fields are given
|
|
|
|
if len(req.Data) == 0 {
|
2015-12-09 15:39:22 +00:00
|
|
|
return logical.ErrorResponse("missing data fields"), nil
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// JSON encode the data
|
|
|
|
buf, err := json.Marshal(req.Data)
|
|
|
|
if err != nil {
|
2021-05-11 17:12:54 +00:00
|
|
|
return nil, fmt.Errorf("json encoding failed: %w", err)
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write out a new key
|
|
|
|
entry := &logical.StorageEntry{
|
2016-01-19 23:19:38 +00:00
|
|
|
Key: req.Path,
|
2015-03-15 21:26:48 +00:00
|
|
|
Value: buf,
|
|
|
|
}
|
2018-01-19 06:44:44 +00:00
|
|
|
if err := req.Storage.Put(ctx, entry); err != nil {
|
2021-05-11 17:12:54 +00:00
|
|
|
return nil, fmt.Errorf("failed to write: %w", err)
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *PassthroughBackend) handleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-15 21:26:48 +00:00
|
|
|
// Delete the key at the request path
|
2018-01-19 06:44:44 +00:00
|
|
|
if err := req.Storage.Delete(ctx, req.Path); err != nil {
|
2015-03-15 21:26:48 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *PassthroughBackend) handleList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2016-01-19 22:05:01 +00:00
|
|
|
// Right now we only handle directories, so ensure it ends with /; however,
|
|
|
|
// some physical backends may not handle the "/" case properly, so only add
|
|
|
|
// it if we're not listing the root
|
|
|
|
path := req.Path
|
|
|
|
if path != "" && !strings.HasSuffix(path, "/") {
|
|
|
|
path = path + "/"
|
|
|
|
}
|
|
|
|
|
2015-03-15 21:26:48 +00:00
|
|
|
// List the keys at the prefix given by the request
|
2018-01-19 06:44:44 +00:00
|
|
|
keys, err := req.Storage.List(ctx, path)
|
2015-03-15 21:26:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the response
|
2016-01-19 22:05:01 +00:00
|
|
|
return logical.ListResponse(keys), nil
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
2015-03-16 00:07:54 +00:00
|
|
|
|
2015-04-04 03:45:00 +00:00
|
|
|
const passthroughHelp = `
|
2017-09-15 13:02:29 +00:00
|
|
|
The kv backend reads and writes arbitrary secrets to the backend.
|
2015-04-04 03:45:00 +00:00
|
|
|
The secrets are encrypted/decrypted by Vault: they are never stored
|
|
|
|
unencrypted in the backend and the backend never has an opportunity to
|
|
|
|
see the unencrypted value.
|
|
|
|
|
2015-08-20 23:41:25 +00:00
|
|
|
TTLs can be set on a per-secret basis. These TTLs will be sent down
|
2015-04-04 03:45:00 +00:00
|
|
|
when that secret is read, and it is assumed that some outside process will
|
|
|
|
revoke and/or replace the secret at that path.
|
|
|
|
`
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
const passthroughHelpSynopsis = `
|
2015-05-27 21:33:58 +00:00
|
|
|
Pass-through secret storage to the storage backend, allowing you to
|
2015-03-16 00:07:54 +00:00
|
|
|
read/write arbitrary data into secret storage.
|
|
|
|
`
|
|
|
|
|
|
|
|
const passthroughHelpDescription = `
|
|
|
|
The pass-through backend reads and writes arbitrary data into secret storage,
|
|
|
|
encrypting it along the way.
|
|
|
|
|
2015-08-21 00:59:30 +00:00
|
|
|
A TTL can be specified when writing with the "ttl" field. If given, the
|
|
|
|
duration of leases returned by this backend will be set to this value. This
|
|
|
|
can be used as a hint from the writer of a secret to the consumer of a secret
|
|
|
|
that the consumer should re-read the value before the TTL has expired.
|
|
|
|
However, any revocation must be handled by the user of this backend; the lease
|
|
|
|
duration does not affect the provided data in any way.
|
2015-03-16 00:07:54 +00:00
|
|
|
`
|