380 lines
13 KiB
Go
380 lines
13 KiB
Go
package logical
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mitchellh/copystructure"
|
|
)
|
|
|
|
// RequestWrapInfo is a struct that stores information about desired response
|
|
// and seal wrapping behavior
|
|
type RequestWrapInfo struct {
|
|
// Setting to non-zero specifies that the response should be wrapped.
|
|
// Specifies the desired TTL of the wrapping token.
|
|
TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl" sentinel:""`
|
|
|
|
// The format to use for the wrapped response; if not specified it's a bare
|
|
// token
|
|
Format string `json:"format" structs:"format" mapstructure:"format" sentinel:""`
|
|
|
|
// A flag to conforming backends that data for a given request should be
|
|
// seal wrapped
|
|
SealWrap bool `json:"seal_wrap" structs:"seal_wrap" mapstructure:"seal_wrap" sentinel:""`
|
|
}
|
|
|
|
func (r *RequestWrapInfo) SentinelGet(key string) (interface{}, error) {
|
|
if r == nil {
|
|
return nil, nil
|
|
}
|
|
switch key {
|
|
case "ttl":
|
|
return r.TTL, nil
|
|
case "ttl_seconds":
|
|
return int64(r.TTL.Seconds()), nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *RequestWrapInfo) SentinelKeys() []string {
|
|
return []string{
|
|
"ttl",
|
|
"ttl_seconds",
|
|
}
|
|
}
|
|
|
|
type ClientTokenSource uint32
|
|
|
|
const (
|
|
NoClientToken ClientTokenSource = iota
|
|
ClientTokenFromVaultHeader
|
|
ClientTokenFromAuthzHeader
|
|
)
|
|
|
|
type WALState struct {
|
|
ClusterID string
|
|
LocalIndex uint64
|
|
ReplicatedIndex uint64
|
|
}
|
|
|
|
const indexStateCtxKey = "index_state"
|
|
|
|
// IndexStateContext returns a context with an added value holding the index
|
|
// state that should be populated on writes.
|
|
func IndexStateContext(ctx context.Context, state *WALState) context.Context {
|
|
return context.WithValue(ctx, indexStateCtxKey, state)
|
|
}
|
|
|
|
// IndexStateFromContext is a helper to look up if the provided context contains
|
|
// an index state pointer.
|
|
func IndexStateFromContext(ctx context.Context) *WALState {
|
|
s, ok := ctx.Value(indexStateCtxKey).(*WALState)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Request is a struct that stores the parameters and context of a request
|
|
// being made to Vault. It is used to abstract the details of the higher level
|
|
// request protocol from the handlers.
|
|
//
|
|
// Note: Many of these have Sentinel disabled because they are values populated
|
|
// by the router after policy checks; the token namespace would be the right
|
|
// place to access them via Sentinel
|
|
type Request struct {
|
|
// Id is the uuid associated with each request
|
|
ID string `json:"id" structs:"id" mapstructure:"id" sentinel:""`
|
|
|
|
// If set, the name given to the replication secondary where this request
|
|
// originated
|
|
ReplicationCluster string `json:"replication_cluster" structs:"replication_cluster" mapstructure:"replication_cluster" sentinel:""`
|
|
|
|
// Operation is the requested operation type
|
|
Operation Operation `json:"operation" structs:"operation" mapstructure:"operation"`
|
|
|
|
// Path is the full path of the request
|
|
Path string `json:"path" structs:"path" mapstructure:"path" sentinel:""`
|
|
|
|
// Request data is an opaque map that must have string keys.
|
|
Data map[string]interface{} `json:"map" structs:"data" mapstructure:"data"`
|
|
|
|
// Storage can be used to durably store and retrieve state.
|
|
Storage Storage `json:"-" sentinel:""`
|
|
|
|
// Secret will be non-nil only for Revoke and Renew operations
|
|
// to represent the secret that was returned prior.
|
|
Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret" sentinel:""`
|
|
|
|
// Auth will be non-nil only for Renew operations
|
|
// to represent the auth that was returned prior.
|
|
Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth" sentinel:""`
|
|
|
|
// Headers will contain the http headers from the request. This value will
|
|
// be used in the audit broker to ensure we are auditing only the allowed
|
|
// headers.
|
|
Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers" sentinel:""`
|
|
|
|
// Connection will be non-nil only for credential providers to
|
|
// inspect the connection information and potentially use it for
|
|
// authentication/protection.
|
|
Connection *Connection `json:"connection" structs:"connection" mapstructure:"connection"`
|
|
|
|
// ClientToken is provided to the core so that the identity
|
|
// can be verified and ACLs applied. This value is passed
|
|
// through to the logical backends but after being salted and
|
|
// hashed.
|
|
ClientToken string `json:"client_token" structs:"client_token" mapstructure:"client_token" sentinel:""`
|
|
|
|
// ClientTokenAccessor is provided to the core so that the it can get
|
|
// logged as part of request audit logging.
|
|
ClientTokenAccessor string `json:"client_token_accessor" structs:"client_token_accessor" mapstructure:"client_token_accessor" sentinel:""`
|
|
|
|
// DisplayName is provided to the logical backend to help associate
|
|
// dynamic secrets with the source entity. This is not a sensitive
|
|
// name, but is useful for operators.
|
|
DisplayName string `json:"display_name" structs:"display_name" mapstructure:"display_name" sentinel:""`
|
|
|
|
// MountPoint is provided so that a logical backend can generate
|
|
// paths relative to itself. The `Path` is effectively the client
|
|
// request path with the MountPoint trimmed off.
|
|
MountPoint string `json:"mount_point" structs:"mount_point" mapstructure:"mount_point" sentinel:""`
|
|
|
|
// MountType is provided so that a logical backend can make decisions
|
|
// based on the specific mount type (e.g., if a mount type has different
|
|
// aliases, generating different defaults depending on the alias)
|
|
MountType string `json:"mount_type" structs:"mount_type" mapstructure:"mount_type" sentinel:""`
|
|
|
|
// MountAccessor is provided so that identities returned by the authentication
|
|
// backends can be tied to the mount it belongs to.
|
|
MountAccessor string `json:"mount_accessor" structs:"mount_accessor" mapstructure:"mount_accessor" sentinel:""`
|
|
|
|
// WrapInfo contains requested response wrapping parameters
|
|
WrapInfo *RequestWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info" sentinel:""`
|
|
|
|
// ClientTokenRemainingUses represents the allowed number of uses left on the
|
|
// token supplied
|
|
ClientTokenRemainingUses int `json:"client_token_remaining_uses" structs:"client_token_remaining_uses" mapstructure:"client_token_remaining_uses"`
|
|
|
|
// EntityID is the identity of the caller extracted out of the token used
|
|
// to make this request
|
|
EntityID string `json:"entity_id" structs:"entity_id" mapstructure:"entity_id" sentinel:""`
|
|
|
|
// PolicyOverride indicates that the requestor wishes to override
|
|
// soft-mandatory Sentinel policies
|
|
PolicyOverride bool `json:"policy_override" structs:"policy_override" mapstructure:"policy_override"`
|
|
|
|
// Whether the request is unauthenticated, as in, had no client token
|
|
// attached. Useful in some situations where the client token is not made
|
|
// accessible.
|
|
Unauthenticated bool `json:"unauthenticated" structs:"unauthenticated" mapstructure:"unauthenticated"`
|
|
|
|
// MFACreds holds the parsed MFA information supplied over the API as part of
|
|
// X-Vault-MFA header
|
|
MFACreds MFACreds `json:"mfa_creds" structs:"mfa_creds" mapstructure:"mfa_creds" sentinel:""`
|
|
|
|
// Cached token entry. This avoids another lookup in request handling when
|
|
// we've already looked it up at http handling time. Note that this token
|
|
// has not been "used", as in it will not properly take into account use
|
|
// count limitations. As a result this field should only ever be used for
|
|
// transport to a function that would otherwise do a lookup and then
|
|
// properly use the token.
|
|
tokenEntry *TokenEntry
|
|
|
|
// For replication, contains the last WAL on the remote side after handling
|
|
// the request, used for best-effort avoidance of stale read-after-write
|
|
lastRemoteWAL uint64
|
|
|
|
// ControlGroup holds the authorizations that have happened on this
|
|
// request
|
|
ControlGroup *ControlGroup `json:"control_group" structs:"control_group" mapstructure:"control_group" sentinel:""`
|
|
|
|
// ClientTokenSource tells us where the client token was sourced from, so
|
|
// we can delete it before sending off to plugins
|
|
ClientTokenSource ClientTokenSource
|
|
|
|
// HTTPRequest, if set, can be used to access fields from the HTTP request
|
|
// that generated this logical.Request object, such as the request body.
|
|
HTTPRequest *http.Request `json:"-" sentinel:""`
|
|
|
|
// ResponseWriter if set can be used to stream a response value to the http
|
|
// request that generated this logical.Request object.
|
|
ResponseWriter *HTTPResponseWriter `json:"-" sentinel:""`
|
|
|
|
// requiredState is used internally to propagate the X-Vault-Index request
|
|
// header to later levels of request processing that operate only on
|
|
// logical.Request.
|
|
requiredState []string
|
|
|
|
// responseState is used internally to propagate the state that should appear
|
|
// in response headers; it's attached to the request rather than the response
|
|
// because not all requests yields non-nil responses.
|
|
responseState *WALState
|
|
|
|
// ClientID is the identity of the caller. If the token is associated with an
|
|
// entity, it will be the same as the EntityID . If the token has no entity,
|
|
// this will be the sha256(sorted policies + namespace) associated with the
|
|
// client token.
|
|
ClientID string `json:"client_id" structs:"client_id" mapstructure:"client_id" sentinel:""`
|
|
}
|
|
|
|
// Clone returns a deep copy of the request by using copystructure
|
|
func (r *Request) Clone() (*Request, error) {
|
|
cpy, err := copystructure.Copy(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cpy.(*Request), nil
|
|
}
|
|
|
|
// Get returns a data field and guards for nil Data
|
|
func (r *Request) Get(key string) interface{} {
|
|
if r.Data == nil {
|
|
return nil
|
|
}
|
|
return r.Data[key]
|
|
}
|
|
|
|
// GetString returns a data field as a string
|
|
func (r *Request) GetString(key string) string {
|
|
raw := r.Get(key)
|
|
s, _ := raw.(string)
|
|
return s
|
|
}
|
|
|
|
func (r *Request) GoString() string {
|
|
return fmt.Sprintf("*%#v", *r)
|
|
}
|
|
|
|
func (r *Request) SentinelGet(key string) (interface{}, error) {
|
|
switch key {
|
|
case "path":
|
|
// Sanitize it here so that it's consistent in policies
|
|
return strings.TrimPrefix(r.Path, "/"), nil
|
|
|
|
case "wrapping", "wrap_info":
|
|
// If the pointer is nil accessing the wrap info is considered
|
|
// "undefined" so this allows us to instead discover a TTL of zero
|
|
if r.WrapInfo == nil {
|
|
return &RequestWrapInfo{}, nil
|
|
}
|
|
return r.WrapInfo, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *Request) SentinelKeys() []string {
|
|
return []string{
|
|
"path",
|
|
"wrapping",
|
|
"wrap_info",
|
|
}
|
|
}
|
|
|
|
func (r *Request) LastRemoteWAL() uint64 {
|
|
return r.lastRemoteWAL
|
|
}
|
|
|
|
func (r *Request) SetLastRemoteWAL(last uint64) {
|
|
r.lastRemoteWAL = last
|
|
}
|
|
|
|
func (r *Request) RequiredState() []string {
|
|
return r.requiredState
|
|
}
|
|
|
|
func (r *Request) SetRequiredState(state []string) {
|
|
r.requiredState = state
|
|
}
|
|
|
|
func (r *Request) ResponseState() *WALState {
|
|
return r.responseState
|
|
}
|
|
|
|
func (r *Request) SetResponseState(w *WALState) {
|
|
r.responseState = w
|
|
}
|
|
|
|
func (r *Request) TokenEntry() *TokenEntry {
|
|
return r.tokenEntry
|
|
}
|
|
|
|
func (r *Request) SetTokenEntry(te *TokenEntry) {
|
|
r.tokenEntry = te
|
|
}
|
|
|
|
// RenewRequest creates the structure of the renew request.
|
|
func RenewRequest(path string, secret *Secret, data map[string]interface{}) *Request {
|
|
return &Request{
|
|
Operation: RenewOperation,
|
|
Path: path,
|
|
Data: data,
|
|
Secret: secret,
|
|
}
|
|
}
|
|
|
|
// RenewAuthRequest creates the structure of the renew request for an auth.
|
|
func RenewAuthRequest(path string, auth *Auth, data map[string]interface{}) *Request {
|
|
return &Request{
|
|
Operation: RenewOperation,
|
|
Path: path,
|
|
Data: data,
|
|
Auth: auth,
|
|
}
|
|
}
|
|
|
|
// RevokeRequest creates the structure of the revoke request.
|
|
func RevokeRequest(path string, secret *Secret, data map[string]interface{}) *Request {
|
|
return &Request{
|
|
Operation: RevokeOperation,
|
|
Path: path,
|
|
Data: data,
|
|
Secret: secret,
|
|
}
|
|
}
|
|
|
|
// RollbackRequest creates the structure of the revoke request.
|
|
func RollbackRequest(path string) *Request {
|
|
return &Request{
|
|
Operation: RollbackOperation,
|
|
Path: path,
|
|
Data: make(map[string]interface{}),
|
|
}
|
|
}
|
|
|
|
// Operation is an enum that is used to specify the type
|
|
// of request being made
|
|
type Operation string
|
|
|
|
const (
|
|
// The operations below are called per path
|
|
CreateOperation Operation = "create"
|
|
ReadOperation = "read"
|
|
UpdateOperation = "update"
|
|
PatchOperation = "patch"
|
|
DeleteOperation = "delete"
|
|
ListOperation = "list"
|
|
HelpOperation = "help"
|
|
AliasLookaheadOperation = "alias-lookahead"
|
|
|
|
// The operations below are called globally, the path is less relevant.
|
|
RevokeOperation Operation = "revoke"
|
|
RenewOperation = "renew"
|
|
RollbackOperation = "rollback"
|
|
)
|
|
|
|
type MFACreds map[string][]string
|
|
|
|
// InitializationRequest stores the parameters and context of an Initialize()
|
|
// call being made to a logical.Backend.
|
|
type InitializationRequest struct {
|
|
|
|
// Storage can be used to durably store and retrieve state.
|
|
Storage Storage
|
|
}
|