2016-05-02 04:08:07 +00:00
package vault
import (
2016-09-29 04:01:28 +00:00
"fmt"
2016-05-02 04:08:07 +00:00
"strings"
"time"
"github.com/armon/go-metrics"
2016-05-16 20:11:33 +00:00
"github.com/hashicorp/go-multierror"
2017-02-16 20:15:02 +00:00
"github.com/hashicorp/vault/helper/consts"
2016-09-29 19:03:47 +00:00
"github.com/hashicorp/vault/helper/jsonutil"
2016-08-08 21:42:25 +00:00
"github.com/hashicorp/vault/helper/policyutil"
2016-05-02 04:08:07 +00:00
"github.com/hashicorp/vault/helper/strutil"
2017-04-24 19:15:01 +00:00
"github.com/hashicorp/vault/helper/wrapping"
2016-05-02 04:08:07 +00:00
"github.com/hashicorp/vault/logical"
)
// HandleRequest is used to handle a new incoming request
func ( c * Core ) HandleRequest ( req * logical . Request ) ( resp * logical . Response , err error ) {
c . stateLock . RLock ( )
defer c . stateLock . RUnlock ( )
if c . sealed {
2017-02-16 20:15:02 +00:00
return nil , consts . ErrSealed
2016-05-02 04:08:07 +00:00
}
if c . standby {
2017-02-16 20:15:02 +00:00
return nil , consts . ErrStandby
2016-05-02 04:08:07 +00:00
}
// Allowing writing to a path ending in / makes it extremely difficult to
2017-09-15 13:02:29 +00:00
// understand user intent for the filesystem-like backends (kv,
2016-05-02 04:08:07 +00:00
// cubbyhole) -- did they want a key named foo/ or did they want to write
// to a directory foo/ with no (or forgotten) key, or...? It also affects
// lookup, because paths ending in / are considered prefixes by some
// backends. Basically, it's all just terrible, so don't allow it.
if strings . HasSuffix ( req . Path , "/" ) &&
( req . Operation == logical . UpdateOperation ||
req . Operation == logical . CreateOperation ) {
return logical . ErrorResponse ( "cannot write to a path ending in '/'" ) , nil
}
var auth * logical . Auth
if c . router . LoginPath ( req . Path ) {
resp , auth , err = c . handleLoginRequest ( req )
} else {
resp , auth , err = c . handleRequest ( req )
}
// Ensure we don't leak internal data
if resp != nil {
if resp . Secret != nil {
resp . Secret . InternalData = nil
}
if resp . Auth != nil {
resp . Auth . InternalData = nil
}
}
// We are wrapping if there is anything to wrap (not a nil response) and a
2016-09-29 04:01:28 +00:00
// 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
2016-05-02 04:08:07 +00:00
2016-05-16 20:11:33 +00:00
if wrapping {
2016-09-29 04:01:28 +00:00
cubbyResp , cubbyErr := c . wrapInCubbyhole ( req , resp )
2016-05-16 20:11:33 +00:00
// If not successful, returns either an error response from the
2016-09-29 04:01:28 +00:00
// 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 ,
2017-06-05 14:52:43 +00:00
Warnings : resp . Warnings ,
2016-09-29 04:01:28 +00:00
}
resp = wrappingResp
2016-05-02 04:08:07 +00:00
}
}
2016-09-29 19:03:47 +00:00
auditResp := resp
// When unwrapping we want to log the actual response that will be written
// out. We still want to return the raw value to avoid automatic updating
// to any of it.
if req . Path == "sys/wrapping/unwrap" &&
resp != nil &&
resp . Data != nil &&
resp . Data [ logical . HTTPRawBody ] != nil {
// Decode the JSON
httpResp := & logical . HTTPResponse { }
err := jsonutil . DecodeJSON ( resp . Data [ logical . HTTPRawBody ] . ( [ ] byte ) , httpResp )
if err != nil {
c . logger . Error ( "core: failed to unmarshal wrapped HTTP response for audit logging" , "error" , err )
return nil , ErrInternalError
}
auditResp = logical . HTTPResponseToLogicalResponse ( httpResp )
}
2016-05-02 04:08:07 +00:00
// Create an audit trail of the response
2017-02-02 19:49:20 +00:00
if auditErr := c . auditBroker . LogResponse ( auth , req , auditResp , c . auditedHeaders , err ) ; auditErr != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to audit response" , "request_path" , req . Path , "error" , auditErr )
2016-05-02 04:08:07 +00:00
return nil , ErrInternalError
}
return
}
func ( c * Core ) handleRequest ( req * logical . Request ) ( retResp * logical . Response , retAuth * logical . Auth , retErr error ) {
defer metrics . MeasureSince ( [ ] string { "core" , "handle_request" } , time . Now ( ) )
// Validate the token
2016-05-02 07:11:14 +00:00
auth , te , ctErr := c . checkToken ( req )
// We run this logic first because we want to decrement the use count even in the case of an error
2016-05-02 04:08:07 +00:00
if te != nil {
2016-05-02 07:11:14 +00:00
// Attempt to use the token (decrement NumUses)
var err error
te , err = c . tokenStore . UseToken ( te )
if err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to use token" , "error" , err )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , nil , retErr
2016-05-02 07:11:14 +00:00
}
if te == nil {
// Token has been revoked by this point
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , logical . ErrPermissionDenied )
return nil , nil , retErr
2016-05-02 07:11:14 +00:00
}
if te . NumUses == - 1 {
// We defer a revocation until after logic has run, since this is a
// valid request (this is the token's final use). We pass the ID in
// directly just to be safe in case something else modifies te later.
defer func ( id string ) {
err = c . tokenStore . Revoke ( id )
if err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to revoke token" , "error" , err )
2016-05-02 07:11:14 +00:00
retResp = nil
retAuth = nil
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
2016-05-02 07:11:14 +00:00
}
if retResp != nil && retResp . Secret != nil &&
// Some backends return a TTL even without a Lease ID
retResp . Secret . LeaseID != "" {
retResp = logical . ErrorResponse ( "Secret cannot be returned; token had one use left, so leased credentials were immediately revoked." )
return
}
} ( te . ID )
}
2016-05-02 04:08:07 +00:00
}
2016-05-02 07:11:14 +00:00
if ctErr != nil {
2016-05-02 04:08:07 +00:00
// If it is an internal error we return that, otherwise we
// return invalid request so that the status codes can be correct
var errType error
2016-05-02 07:11:14 +00:00
switch ctErr {
2016-05-02 04:08:07 +00:00
case ErrInternalError , logical . ErrPermissionDenied :
2016-05-02 07:11:14 +00:00
errType = ctErr
2016-05-02 04:08:07 +00:00
default :
errType = logical . ErrInvalidRequest
}
2017-02-02 19:49:20 +00:00
if err := c . auditBroker . LogRequest ( auth , req , c . auditedHeaders , ctErr ) ; err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to audit request" , "path" , req . Path , "error" , err )
2016-05-02 04:08:07 +00:00
}
2016-05-16 20:11:33 +00:00
if errType != nil {
retErr = multierror . Append ( retErr , errType )
}
2017-08-15 20:44:16 +00:00
if ctErr == ErrInternalError {
return nil , auth , retErr
}
2017-06-05 22:04:31 +00:00
return logical . ErrorResponse ( ctErr . Error ( ) ) , auth , retErr
2016-05-02 04:08:07 +00:00
}
// Attach the display name
req . DisplayName = auth . DisplayName
// Create an audit trail of the request
2017-02-02 19:49:20 +00:00
if err := c . auditBroker . LogRequest ( auth , req , c . auditedHeaders , nil ) ; err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to audit request" , "path" , req . Path , "error" , err )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
2016-05-02 04:08:07 +00:00
}
// Route the request
2017-02-17 04:09:39 +00:00
resp , routeErr := c . router . Route ( req )
2016-05-16 20:11:33 +00:00
if resp != nil {
2016-11-11 20:12:11 +00:00
// If wrapping is used, use the shortest between the request and response
var wrapTTL time . Duration
2017-08-02 22:28:58 +00:00
var wrapFormat , creationPath string
2016-05-16 20:11:33 +00:00
2016-11-11 20:12:11 +00:00
// Ensure no wrap info information is set other than, possibly, the TTL
if resp . WrapInfo != nil {
if resp . WrapInfo . TTL > 0 {
wrapTTL = resp . WrapInfo . TTL
}
2017-01-04 21:44:03 +00:00
wrapFormat = resp . WrapInfo . Format
2017-08-02 22:28:58 +00:00
creationPath = resp . WrapInfo . CreationPath
2016-11-11 20:12:11 +00:00
resp . WrapInfo = nil
}
2017-01-04 21:44:03 +00:00
if req . WrapInfo != nil {
if req . WrapInfo . TTL > 0 {
switch {
case wrapTTL == 0 :
wrapTTL = req . WrapInfo . TTL
case req . WrapInfo . TTL < wrapTTL :
wrapTTL = req . WrapInfo . TTL
}
}
// If the wrap format hasn't been set by the response, set it to
// the request format
if req . WrapInfo . Format != "" && wrapFormat == "" {
wrapFormat = req . WrapInfo . Format
2016-11-11 20:12:11 +00:00
}
}
if wrapTTL > 0 {
2017-04-24 19:15:01 +00:00
resp . WrapInfo = & wrapping . ResponseWrapInfo {
2017-08-02 22:28:58 +00:00
TTL : wrapTTL ,
Format : wrapFormat ,
CreationPath : creationPath ,
2016-05-16 20:11:33 +00:00
}
}
}
2016-05-02 04:08:07 +00:00
// If there is a secret, we must register it with the expiration manager.
// We exclude renewal of a lease, since it does not need to be re-registered
2017-06-20 16:34:00 +00:00
if resp != nil && resp . Secret != nil && ! strings . HasPrefix ( req . Path , "sys/renew" ) &&
! strings . HasPrefix ( req . Path , "sys/leases/renew" ) {
2016-05-02 04:08:07 +00:00
// Get the SystemView for the mount
sysView := c . router . MatchingSystemView ( req . Path )
if sysView == nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: unable to retrieve system view from router" )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
2016-05-02 04:08:07 +00:00
}
// Apply the default lease if none given
if resp . Secret . TTL == 0 {
resp . Secret . TTL = sysView . DefaultLeaseTTL ( )
}
// Limit the lease duration
maxTTL := sysView . MaxLeaseTTL ( )
if resp . Secret . TTL > maxTTL {
resp . Secret . TTL = maxTTL
}
2017-09-15 13:02:29 +00:00
// KV mounts should return the TTL but not register
2016-05-02 04:08:07 +00:00
// for a lease as this provides a massive slowdown
registerLease := true
matchingBackend := c . router . MatchingBackend ( req . Path )
if matchingBackend == nil {
2017-09-15 13:02:29 +00:00
c . logger . Error ( "core: unable to retrieve kv backend from router" )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
2016-05-02 04:08:07 +00:00
}
if ptbe , ok := matchingBackend . ( * PassthroughBackend ) ; ok {
if ! ptbe . GeneratesLeases ( ) {
registerLease = false
resp . Secret . Renewable = false
}
}
if registerLease {
leaseID , err := c . expiration . Register ( req , resp )
if err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to register lease" , "request_path" , req . Path , "error" , err )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
2016-05-02 04:08:07 +00:00
}
resp . Secret . LeaseID = leaseID
}
}
// Only the token store is allowed to return an auth block, for any
// other request this is an internal error. We exclude renewal of a token,
// since it does not need to be re-registered
if resp != nil && resp . Auth != nil && ! strings . HasPrefix ( req . Path , "auth/token/renew" ) {
if ! strings . HasPrefix ( req . Path , "auth/token/" ) {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: unexpected Auth response for non-token backend" , "request_path" , req . Path )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
2016-05-02 04:08:07 +00:00
}
// Register with the expiration manager. We use the token's actual path
// here because roles allow suffixes.
te , err := c . tokenStore . Lookup ( resp . Auth . ClientToken )
if err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to look up token" , "error" , err )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
2017-05-03 18:29:57 +00:00
return nil , auth , retErr
2016-05-02 04:08:07 +00:00
}
if err := c . expiration . RegisterAuth ( te . Path , resp . Auth ) ; err != nil {
2017-05-03 18:29:57 +00:00
c . tokenStore . Revoke ( te . ID )
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to register token lease" , "request_path" , req . Path , "error" , err )
2016-05-16 20:11:33 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
2016-05-02 04:08:07 +00:00
}
}
2016-09-29 04:01:28 +00:00
if resp != nil &&
req . Path == "cubbyhole/response" &&
len ( te . Policies ) == 1 &&
te . Policies [ 0 ] == responseWrappingPolicyName {
2016-09-29 21:44:15 +00:00
resp . AddWarning ( "Reading from 'cubbyhole/response' is deprecated. Please use sys/wrapping/unwrap to unwrap responses, as it provides additional security checks and other benefits." )
2016-09-29 04:01:28 +00:00
}
2016-05-02 04:08:07 +00:00
// Return the response and error
2017-02-17 04:09:39 +00:00
if routeErr != nil {
retErr = multierror . Append ( retErr , routeErr )
2016-05-16 20:11:33 +00:00
}
return resp , auth , retErr
2016-05-02 04:08:07 +00:00
}
// handleLoginRequest is used to handle a login request, which is an
// unauthenticated request to the backend.
2016-08-08 21:56:14 +00:00
func ( c * Core ) handleLoginRequest ( req * logical . Request ) ( * logical . Response , * logical . Auth , error ) {
2016-05-02 04:08:07 +00:00
defer metrics . MeasureSince ( [ ] string { "core" , "handle_login_request" } , time . Now ( ) )
// Create an audit trail of the request, auth is not available on login requests
2017-02-02 19:49:20 +00:00
if err := c . auditBroker . LogRequest ( nil , req , c . auditedHeaders , nil ) ; err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to audit request" , "path" , req . Path , "error" , err )
2016-05-02 04:08:07 +00:00
return nil , nil , ErrInternalError
}
2016-08-08 21:32:37 +00:00
// The token store uses authentication even when creating a new token,
// so it's handled in handleRequest. It should not be reached here.
if strings . HasPrefix ( req . Path , "auth/token/" ) {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: unexpected login request for token backend" , "request_path" , req . Path )
2016-08-08 21:56:14 +00:00
return nil , nil , ErrInternalError
2016-08-08 21:32:37 +00:00
}
2016-05-02 04:08:07 +00:00
// Route the request
2017-02-17 04:09:39 +00:00
resp , routeErr := c . router . Route ( req )
2016-07-05 15:46:21 +00:00
if resp != nil {
2016-11-11 20:12:11 +00:00
// If wrapping is used, use the shortest between the request and response
var wrapTTL time . Duration
2017-08-02 22:28:58 +00:00
var wrapFormat , creationPath string
2016-07-05 15:46:21 +00:00
2016-11-11 20:12:11 +00:00
// Ensure no wrap info information is set other than, possibly, the TTL
if resp . WrapInfo != nil {
if resp . WrapInfo . TTL > 0 {
wrapTTL = resp . WrapInfo . TTL
}
2017-01-04 21:44:03 +00:00
wrapFormat = resp . WrapInfo . Format
2017-08-02 22:28:58 +00:00
creationPath = resp . WrapInfo . CreationPath
2016-11-11 20:12:11 +00:00
resp . WrapInfo = nil
}
2017-01-04 21:44:03 +00:00
if req . WrapInfo != nil {
if req . WrapInfo . TTL > 0 {
switch {
case wrapTTL == 0 :
wrapTTL = req . WrapInfo . TTL
case req . WrapInfo . TTL < wrapTTL :
wrapTTL = req . WrapInfo . TTL
}
}
if req . WrapInfo . Format != "" && wrapFormat == "" {
wrapFormat = req . WrapInfo . Format
2016-11-11 20:12:11 +00:00
}
}
if wrapTTL > 0 {
2017-04-24 19:15:01 +00:00
resp . WrapInfo = & wrapping . ResponseWrapInfo {
2017-08-02 22:28:58 +00:00
TTL : wrapTTL ,
Format : wrapFormat ,
CreationPath : creationPath ,
2016-07-05 15:46:21 +00:00
}
}
}
2016-05-02 04:08:07 +00:00
// A login request should never return a secret!
if resp != nil && resp . Secret != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: unexpected Secret response for login path" , "request_path" , req . Path )
2016-05-02 04:08:07 +00:00
return nil , nil , ErrInternalError
}
// If the response generated an authentication, then generate the token
var auth * logical . Auth
if resp != nil && resp . Auth != nil {
auth = resp . Auth
2016-08-08 21:32:37 +00:00
if strutil . StrListSubset ( auth . Policies , [ ] string { "root" } ) {
return logical . ErrorResponse ( "authentication backends cannot create root tokens" ) , nil , logical . ErrInvalidRequest
}
2016-05-02 04:08:07 +00:00
// Determine the source of the login
source := c . router . MatchingMount ( req . Path )
source = strings . TrimPrefix ( source , credentialRoutePrefix )
source = strings . Replace ( source , "/" , "-" , - 1 )
// Prepend the source to the display name
auth . DisplayName = strings . TrimSuffix ( source + auth . DisplayName , "-" )
sysView := c . router . MatchingSystemView ( req . Path )
if sysView == nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: unable to look up sys view for login path" , "request_path" , req . Path )
2016-05-02 04:08:07 +00:00
return nil , nil , ErrInternalError
}
2016-08-08 21:32:37 +00:00
// Set the default lease if not provided
if auth . TTL == 0 {
2016-05-02 04:08:07 +00:00
auth . TTL = sysView . DefaultLeaseTTL ( )
}
// Limit the lease duration
if auth . TTL > sysView . MaxLeaseTTL ( ) {
auth . TTL = sysView . MaxLeaseTTL ( )
}
// Generate a token
te := TokenEntry {
Path : req . Path ,
Policies : auth . Policies ,
Meta : auth . Metadata ,
DisplayName : auth . DisplayName ,
CreationTime : time . Now ( ) . Unix ( ) ,
TTL : auth . TTL ,
2017-03-03 14:31:20 +00:00
NumUses : auth . NumUses ,
2016-05-02 04:08:07 +00:00
}
2016-08-08 21:42:25 +00:00
te . Policies = policyutil . SanitizePolicies ( te . Policies , true )
2016-08-08 21:32:37 +00:00
2016-09-29 04:01:28 +00:00
// 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
}
}
2016-05-02 04:08:07 +00:00
if err := c . tokenStore . create ( & te ) ; err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to create token" , "error" , err )
2016-05-02 04:08:07 +00:00
return nil , auth , ErrInternalError
}
// Populate the client token and accessor
auth . ClientToken = te . ID
auth . Accessor = te . Accessor
auth . Policies = te . Policies
// Register with the expiration manager
if err := c . expiration . RegisterAuth ( te . Path , auth ) ; err != nil {
2017-05-03 18:29:57 +00:00
c . tokenStore . Revoke ( te . ID )
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to register token lease" , "request_path" , req . Path , "error" , err )
2016-05-02 04:08:07 +00:00
return nil , auth , ErrInternalError
}
// Attach the display name, might be used by audit backends
req . DisplayName = auth . DisplayName
}
2017-02-17 04:09:39 +00:00
return resp , auth , routeErr
2016-05-02 04:08:07 +00:00
}