2016-05-02 04:08:07 +00:00
package vault
import (
2018-01-08 18:31:38 +00:00
"context"
2018-05-31 00:30:50 +00:00
"errors"
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"
2018-05-31 00:30:50 +00:00
sockaddr "github.com/hashicorp/go-sockaddr"
2018-03-02 17:18:39 +00:00
"github.com/hashicorp/vault/audit"
2017-02-16 20:15:02 +00:00
"github.com/hashicorp/vault/helper/consts"
2018-05-31 00:30:50 +00:00
"github.com/hashicorp/vault/helper/errutil"
2017-10-11 17:21:20 +00:00
"github.com/hashicorp/vault/helper/identity"
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"
2018-04-03 16:20:20 +00:00
"github.com/hashicorp/vault/logical/framework"
2016-05-02 04:08:07 +00:00
)
2017-10-23 18:59:37 +00:00
const (
replTimeout = 10 * time . Second
)
2018-05-31 00:30:50 +00:00
// fetchEntityAndDerivedPolicies returns the entity object for the given entity
// ID. If the entity is merged into a different entity object, the entity into
// which the given entity ID is merged into will be returned. This function
// also returns the cumulative list of policies that the entity is entitled to.
// This list includes the policies from the entity itself and from all the
// groups in which the given entity ID is a member of.
func ( c * Core ) fetchEntityAndDerivedPolicies ( entityID string ) ( * identity . Entity , [ ] string , error ) {
if entityID == "" || c . identityStore == nil {
return nil , nil , nil
}
//c.logger.Debug("entity set on the token", "entity_id", te.EntityID)
// Fetch the entity
entity , err := c . identityStore . MemDBEntityByID ( entityID , false )
if err != nil {
c . logger . Error ( "failed to lookup entity using its ID" , "error" , err )
return nil , nil , err
}
if entity == nil {
// If there was no corresponding entity object found, it is
// possible that the entity got merged into another entity. Try
// finding entity based on the merged entity index.
entity , err = c . identityStore . MemDBEntityByMergedEntityID ( entityID , false )
if err != nil {
c . logger . Error ( "failed to lookup entity in merged entity ID index" , "error" , err )
return nil , nil , err
}
}
var policies [ ] string
if entity != nil {
//c.logger.Debug("entity successfully fetched; adding entity policies to token's policies to create ACL")
// Attach the policies on the entity
policies = append ( policies , entity . Policies ... )
groupPolicies , err := c . identityStore . groupPoliciesByEntityID ( entity . ID )
if err != nil {
c . logger . Error ( "failed to fetch group policies" , "error" , err )
return nil , nil , err
}
// Attach the policies from all the groups
policies = append ( policies , groupPolicies ... )
}
return entity , policies , err
}
func ( c * Core ) fetchACLTokenEntryAndEntity ( req * logical . Request ) ( * ACL , * TokenEntry , * identity . Entity , error ) {
defer metrics . MeasureSince ( [ ] string { "core" , "fetch_acl_and_token" } , time . Now ( ) )
// Ensure there is a client token
if req . ClientToken == "" {
return nil , nil , nil , fmt . Errorf ( "missing client token" )
}
if c . tokenStore == nil {
c . logger . Error ( "token store is unavailable" )
return nil , nil , nil , ErrInternalError
}
// Resolve the token policy
te , err := c . tokenStore . Lookup ( c . activeContext , req . ClientToken )
if err != nil {
c . logger . Error ( "failed to lookup token" , "error" , err )
return nil , nil , nil , ErrInternalError
}
// Ensure the token is valid
if te == nil {
return nil , nil , nil , logical . ErrPermissionDenied
}
// CIDR checks bind all tokens except non-expiring root tokens
if te . TTL != 0 && len ( te . BoundCIDRs ) > 0 {
var valid bool
remoteSockAddr , err := sockaddr . NewSockAddr ( req . Connection . RemoteAddr )
if err != nil {
if c . Logger ( ) . IsDebug ( ) {
c . Logger ( ) . Debug ( "could not parse remote addr into sockaddr" , "error" , err , "remote_addr" , req . Connection . RemoteAddr )
}
return nil , nil , nil , logical . ErrPermissionDenied
}
for _ , cidr := range te . BoundCIDRs {
if cidr . Contains ( remoteSockAddr ) {
valid = true
break
}
}
if ! valid {
return nil , nil , nil , logical . ErrPermissionDenied
}
}
tokenPolicies := te . Policies
entity , derivedPolicies , err := c . fetchEntityAndDerivedPolicies ( te . EntityID )
if err != nil {
return nil , nil , nil , ErrInternalError
}
tokenPolicies = append ( tokenPolicies , derivedPolicies ... )
// Construct the corresponding ACL object
acl , err := c . policyStore . ACL ( c . activeContext , tokenPolicies ... )
if err != nil {
c . logger . Error ( "failed to construct ACL" , "error" , err )
return nil , nil , nil , ErrInternalError
}
return acl , te , entity , nil
}
func ( c * Core ) checkToken ( ctx context . Context , req * logical . Request , unauth bool ) ( * logical . Auth , * TokenEntry , error ) {
defer metrics . MeasureSince ( [ ] string { "core" , "check_token" } , time . Now ( ) )
var acl * ACL
var te * TokenEntry
var entity * identity . Entity
var err error
// Even if unauth, if a token is provided, there's little reason not to
// gather as much info as possible for the audit log and to e.g. control
// trace mode for EGPs.
if ! unauth || ( unauth && req . ClientToken != "" ) {
acl , te , entity , err = c . fetchACLTokenEntryAndEntity ( req )
// In the unauth case we don't want to fail the command, since it's
// unauth, we just have no information to attach to the request, so
// ignore errors...this was best-effort anyways
if err != nil && ! unauth {
return nil , te , err
}
}
if entity != nil && entity . Disabled {
return nil , te , logical . ErrPermissionDenied
}
// Check if this is a root protected path
rootPath := c . router . RootPath ( req . Path )
if rootPath && unauth {
return nil , nil , errors . New ( "cannot access root path in unauthenticated request" )
}
// When we receive a write of either type, rather than require clients to
// PUT/POST and trust the operation, we ask the backend to give us the real
// skinny -- if the backend implements an existence check, it can tell us
// whether a particular resource exists. Then we can mark it as an update
// or creation as appropriate.
if req . Operation == logical . CreateOperation || req . Operation == logical . UpdateOperation {
checkExists , resourceExists , err := c . router . RouteExistenceCheck ( ctx , req )
switch err {
case logical . ErrUnsupportedPath :
// fail later via bad path to avoid confusing items in the log
checkExists = false
case nil :
// Continue on
default :
c . logger . Error ( "failed to run existence check" , "error" , err )
if _ , ok := err . ( errutil . UserError ) ; ok {
return nil , nil , err
} else {
return nil , nil , ErrInternalError
}
}
switch {
case checkExists == false :
// No existence check, so always treat it as an update operation, which is how it is pre 0.5
req . Operation = logical . UpdateOperation
case resourceExists == true :
// It exists, so force an update operation
req . Operation = logical . UpdateOperation
case resourceExists == false :
// It doesn't exist, force a create operation
req . Operation = logical . CreateOperation
default :
panic ( "unreachable code" )
}
}
// Create the auth response
auth := & logical . Auth {
ClientToken : req . ClientToken ,
Accessor : req . ClientTokenAccessor ,
}
if te != nil {
auth . Policies = te . Policies
auth . Metadata = te . Meta
auth . DisplayName = te . DisplayName
auth . EntityID = te . EntityID
// Store the entity ID in the request object
req . EntityID = te . EntityID
}
// Check the standard non-root ACLs. Return the token entry if it's not
// allowed so we can decrement the use count.
authResults := c . performPolicyChecks ( ctx , acl , te , req , entity , & PolicyCheckOpts {
Unauth : unauth ,
RootPrivsRequired : rootPath ,
} )
if authResults . Error . ErrorOrNil ( ) != nil {
return auth , te , authResults . Error
}
if ! authResults . Allowed {
// Return auth for audit logging even if not allowed
return auth , te , logical . ErrPermissionDenied
}
return auth , te , nil
}
2016-05-02 04:08:07 +00:00
// 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
}
2018-01-19 06:44:44 +00:00
ctx , cancel := context . WithCancel ( c . activeContext )
2018-01-08 18:31:38 +00:00
defer cancel ( )
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 ) {
2018-01-08 18:31:38 +00:00
resp , auth , err = c . handleLoginRequest ( ctx , req )
2016-05-02 04:08:07 +00:00
} else {
2018-01-08 18:31:38 +00:00
resp , auth , err = c . handleRequest ( ctx , req )
2016-05-02 04:08:07 +00:00
}
// 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 &&
2017-11-13 20:31:32 +00:00
resp . WrapInfo . TTL != 0 &&
resp . WrapInfo . Token == ""
2016-05-02 04:08:07 +00:00
2016-05-16 20:11:33 +00:00
if wrapping {
2018-01-08 18:31:38 +00:00
cubbyResp , cubbyErr := c . wrapInCubbyhole ( ctx , req , resp , auth )
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
Fix response wrapping from K/V version 2 (#4511)
This takes place in two parts, since working on this exposed an issue
with response wrapping when there is a raw body set. The changes are (in
diff order):
* A CurrentWrappingLookupFunc has been added to return the current
value. This is necessary for the lookahead call since we don't want the
lookahead call to be wrapped.
* Support for unwrapping < 0.6.2 tokens via the API/CLI has been
removed, because we now have backends returning 404s with data and can't
rely on the 404 trick. These can still be read manually via
cubbyhole/response.
* KV preflight version request now ensures that its calls is not
wrapped, and restores any given function after.
* When responding with a raw body, instead of always base64-decoding a
string value and erroring on failure, on failure we assume that it
simply wasn't a base64-encoded value and use it as is.
* A test that fails on master and works now that ensures that raw body
responses that are wrapped and then unwrapped return the expected
values.
* A flag for response data that indicates to the wrapping handling that
the data contained therein is already JSON decoded (more later).
* RespondWithStatusCode now defaults to a string so that the value is
HMAC'd during audit. The function always JSON encodes the body, so
before now it was always returning []byte which would skip HMACing. We
don't know what's in the data, so this is a "better safe than sorry"
issue. If different behavior is needed, backends can always manually
populate the data instead of relying on the helper function.
* We now check unwrapped data after unwrapping to see if there were raw
flags. If so, we try to detect whether the value can be unbase64'd. The
reason is that if it can it was probably originally a []byte and
shouldn't be audit HMAC'd; if not, it was probably originally a string
and should be. In either case, we then set the value as the raw body and
hit the flag indicating that it's already been JSON decoded so not to
try again before auditing. Doing it this way ensures the right typing.
* There is now a check to see if the data coming from unwrapping is
already JSON decoded and if so the decoding is skipped before setting
the audit response.
2018-05-10 19:40:03 +00:00
if resp . Data [ logical . HTTPRawBodyAlreadyJSONDecoded ] != nil {
delete ( resp . Data , logical . HTTPRawBodyAlreadyJSONDecoded )
} else {
httpResp := & logical . HTTPResponse { }
err := jsonutil . DecodeJSON ( resp . Data [ logical . HTTPRawBody ] . ( [ ] byte ) , httpResp )
if err != nil {
c . logger . Error ( "failed to unmarshal wrapped HTTP response for audit logging" , "error" , err )
return nil , ErrInternalError
}
2016-09-29 19:03:47 +00:00
Fix response wrapping from K/V version 2 (#4511)
This takes place in two parts, since working on this exposed an issue
with response wrapping when there is a raw body set. The changes are (in
diff order):
* A CurrentWrappingLookupFunc has been added to return the current
value. This is necessary for the lookahead call since we don't want the
lookahead call to be wrapped.
* Support for unwrapping < 0.6.2 tokens via the API/CLI has been
removed, because we now have backends returning 404s with data and can't
rely on the 404 trick. These can still be read manually via
cubbyhole/response.
* KV preflight version request now ensures that its calls is not
wrapped, and restores any given function after.
* When responding with a raw body, instead of always base64-decoding a
string value and erroring on failure, on failure we assume that it
simply wasn't a base64-encoded value and use it as is.
* A test that fails on master and works now that ensures that raw body
responses that are wrapped and then unwrapped return the expected
values.
* A flag for response data that indicates to the wrapping handling that
the data contained therein is already JSON decoded (more later).
* RespondWithStatusCode now defaults to a string so that the value is
HMAC'd during audit. The function always JSON encodes the body, so
before now it was always returning []byte which would skip HMACing. We
don't know what's in the data, so this is a "better safe than sorry"
issue. If different behavior is needed, backends can always manually
populate the data instead of relying on the helper function.
* We now check unwrapped data after unwrapping to see if there were raw
flags. If so, we try to detect whether the value can be unbase64'd. The
reason is that if it can it was probably originally a []byte and
shouldn't be audit HMAC'd; if not, it was probably originally a string
and should be. In either case, we then set the value as the raw body and
hit the flag indicating that it's already been JSON decoded so not to
try again before auditing. Doing it this way ensures the right typing.
* There is now a check to see if the data coming from unwrapping is
already JSON decoded and if so the decoding is skipped before setting
the audit response.
2018-05-10 19:40:03 +00:00
auditResp = logical . HTTPResponseToLogicalResponse ( httpResp )
}
2016-09-29 19:03:47 +00:00
}
2018-03-02 17:18:39 +00:00
var nonHMACReqDataKeys [ ] string
var nonHMACRespDataKeys [ ] string
entry := c . router . MatchingMountEntry ( req . Path )
if entry != nil {
// Get and set ignored HMAC'd value. Reset those back to empty afterwards.
if rawVals , ok := entry . synthesizedConfigCache . Load ( "audit_non_hmac_request_keys" ) ; ok {
nonHMACReqDataKeys = rawVals . ( [ ] string )
}
// Get and set ignored HMAC'd value. Reset those back to empty afterwards.
if auditResp != nil {
if rawVals , ok := entry . synthesizedConfigCache . Load ( "audit_non_hmac_response_keys" ) ; ok {
nonHMACRespDataKeys = rawVals . ( [ ] string )
}
}
}
2016-05-02 04:08:07 +00:00
// Create an audit trail of the response
2018-03-02 17:18:39 +00:00
logInput := & audit . LogInput {
Auth : auth ,
Request : req ,
Response : auditResp ,
OuterErr : err ,
NonHMACReqDataKeys : nonHMACReqDataKeys ,
NonHMACRespDataKeys : nonHMACRespDataKeys ,
}
if auditErr := c . auditBroker . LogResponse ( ctx , logInput , c . auditedHeaders ) ; auditErr != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "failed to audit response" , "request_path" , req . Path , "error" , auditErr )
2016-05-02 04:08:07 +00:00
return nil , ErrInternalError
}
return
}
2018-01-08 18:31:38 +00:00
func ( c * Core ) handleRequest ( ctx context . Context , req * logical . Request ) ( retResp * logical . Response , retAuth * logical . Auth , retErr error ) {
2016-05-02 04:08:07 +00:00
defer metrics . MeasureSince ( [ ] string { "core" , "handle_request" } , time . Now ( ) )
2018-03-02 17:18:39 +00:00
var nonHMACReqDataKeys [ ] string
entry := c . router . MatchingMountEntry ( req . Path )
if entry != nil {
// Get and set ignored HMAC'd value.
if rawVals , ok := entry . synthesizedConfigCache . Load ( "audit_non_hmac_request_keys" ) ; ok {
nonHMACReqDataKeys = rawVals . ( [ ] string )
}
}
2016-05-02 04:08:07 +00:00
// Validate the token
2018-01-08 18:31:38 +00:00
auth , te , ctErr := c . checkToken ( ctx , req , false )
2016-05-02 07:11:14 +00:00
// 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
2018-01-19 06:44:44 +00:00
te , err = c . tokenStore . UseToken ( ctx , te )
2016-05-02 07:11:14 +00:00
if err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
}
2018-05-10 19:50:02 +00:00
if te . NumUses == tokenRevocationPending {
2016-05-02 07:11:14 +00:00
// 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 ) {
2018-05-10 19:50:02 +00:00
leaseID , err := c . expiration . CreateOrFetchRevocationLeaseByToken ( te )
if err == nil {
err = c . expiration . Revoke ( leaseID )
}
2016-05-02 07:11:14 +00:00
if err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
2017-11-13 20:31:32 +00:00
errType := logical . ErrInvalidRequest
2016-05-02 07:11:14 +00:00
switch ctErr {
2018-04-23 20:50:04 +00:00
case ErrInternalError , logical . ErrPermissionDenied :
2016-05-02 07:11:14 +00:00
errType = ctErr
2016-05-02 04:08:07 +00:00
}
2018-03-02 17:18:39 +00:00
logInput := & audit . LogInput {
Auth : auth ,
Request : req ,
OuterErr : ctErr ,
NonHMACReqDataKeys : nonHMACReqDataKeys ,
}
if err := c . auditBroker . LogRequest ( ctx , logInput , c . auditedHeaders ) ; err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
2018-03-02 17:18:39 +00:00
logInput := & audit . LogInput {
Auth : auth ,
Request : req ,
NonHMACReqDataKeys : nonHMACReqDataKeys ,
}
if err := c . auditBroker . LogRequest ( ctx , logInput , c . auditedHeaders ) ; err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
2018-01-08 18:31:38 +00:00
resp , routeErr := c . router . Route ( ctx , 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
2017-11-09 17:47:42 +00:00
var sealWrap bool
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
2017-11-09 17:47:42 +00:00
sealWrap = resp . WrapInfo . SealWrap
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 ,
2017-11-09 17:47:42 +00:00
SealWrap : sealWrap ,
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" ) {
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
2018-03-21 19:04:27 +00:00
matchingMountEntry := c . router . MatchingMountEntry ( req . Path )
if matchingMountEntry == nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "unable to retrieve kv mount entry 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
}
2018-03-21 19:04:27 +00:00
switch matchingMountEntry . Type {
case "kv" , "generic" :
// If we are kv type, first see if we are an older passthrough
// backend, and otherwise check the mount entry options.
matchingBackend := c . router . MatchingBackend ( req . Path )
if matchingBackend == nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "unable to retrieve kv backend from router" )
2018-03-21 19:04:27 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
}
if ptbe , ok := matchingBackend . ( * PassthroughBackend ) ; ok {
if ! ptbe . GeneratesLeases ( ) {
registerLease = false
resp . Secret . Renewable = false
}
} else if matchingMountEntry . Options == nil || matchingMountEntry . Options [ "leased_passthrough" ] != "true" {
registerLease = false
resp . Secret . Renewable = false
}
case "plugin" :
// If we are a plugin type and the plugin name is "kv" check the
// mount entry options.
if matchingMountEntry . Config . PluginName == "kv" && ( matchingMountEntry . Options == nil || matchingMountEntry . Options [ "leased_passthrough" ] != "true" ) {
2016-05-02 04:08:07 +00:00
registerLease = false
resp . Secret . Renewable = false
}
}
if registerLease {
2018-04-03 16:20:20 +00:00
sysView := c . router . MatchingSystemView ( req . Path )
if sysView == nil {
2018-04-10 01:14:23 +00:00
c . logger . Error ( "unable to look up sys view for login path" , "request_path" , req . Path )
2018-04-03 16:20:20 +00:00
return nil , nil , ErrInternalError
}
ttl , warnings , err := framework . CalculateTTL ( sysView , 0 , resp . Secret . TTL , 0 , resp . Secret . MaxTTL , 0 , time . Time { } )
if err != nil {
return nil , nil , err
}
for _ , warning := range warnings {
resp . AddWarning ( warning )
}
resp . Secret . TTL = ttl
2016-05-02 04:08:07 +00:00
leaseID , err := c . expiration . Register ( req , resp )
if err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
}
}
2017-11-02 20:05:48 +00:00
// If the request was to renew a token, and if there are group aliases set
// in the auth object, then the group memberships should be refreshed
if strings . HasPrefix ( req . Path , "auth/token/renew" ) &&
resp != nil &&
resp . Auth != nil &&
resp . Auth . EntityID != "" &&
2018-04-25 03:10:22 +00:00
resp . Auth . GroupAliases != nil &&
c . identityStore != nil {
2017-11-02 20:05:48 +00:00
err := c . identityStore . refreshExternalGroupMembershipsByEntityID ( resp . Auth . EntityID , resp . Auth . GroupAliases )
if err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "failed to refresh external group memberships" , "error" , err )
2017-11-02 20:05:48 +00:00
retErr = multierror . Append ( retErr , ErrInternalError )
return nil , auth , retErr
}
}
2016-05-02 04:08:07 +00:00
// 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/" ) {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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.
2018-01-19 06:44:44 +00:00
te , err := c . tokenStore . Lookup ( ctx , resp . Auth . ClientToken )
2016-05-02 04:08:07 +00:00
if err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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 {
2018-05-10 19:50:02 +00:00
c . tokenStore . revokeOrphan ( ctx , te . ID )
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
}
2017-11-02 20:05:48 +00:00
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.
2018-01-08 18:31:38 +00:00
func ( c * Core ) handleLoginRequest ( ctx context . Context , req * logical . Request ) ( retResp * logical . Response , retAuth * logical . Auth , retErr error ) {
2016-05-02 04:08:07 +00:00
defer metrics . MeasureSince ( [ ] string { "core" , "handle_login_request" } , time . Now ( ) )
2017-10-23 18:59:37 +00:00
req . Unauthenticated = true
2017-10-23 20:49:46 +00:00
2017-10-23 18:59:37 +00:00
var auth * logical . Auth
2016-05-02 04:08:07 +00:00
// Create an audit trail of the request, auth is not available on login requests
2017-10-23 18:59:37 +00:00
// Create an audit trail of the request. Attach auth if it was returned,
// e.g. if a token was provided.
2018-03-02 17:18:39 +00:00
logInput := & audit . LogInput {
Auth : auth ,
Request : req ,
}
if err := c . auditBroker . LogRequest ( ctx , logInput , c . auditedHeaders ) ; err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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/" ) {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
2018-01-08 18:31:38 +00:00
resp , routeErr := c . router . Route ( ctx , 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
2017-11-09 17:47:42 +00:00
var sealWrap bool
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
2017-11-09 17:47:42 +00:00
sealWrap = resp . WrapInfo . SealWrap
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 ,
2017-11-09 17:47:42 +00:00
SealWrap : sealWrap ,
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 {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
if resp != nil && resp . Auth != nil {
2018-05-09 22:39:55 +00:00
2017-10-11 17:21:20 +00:00
var entity * identity . Entity
2016-05-02 04:08:07 +00:00
auth = resp . Auth
2018-04-23 17:46:14 +00:00
mEntry := c . router . MatchingMountEntry ( req . Path )
2018-04-25 03:10:22 +00:00
if auth . Alias != nil &&
mEntry != nil &&
! mEntry . Local &&
c . identityStore != nil {
2017-10-11 17:21:20 +00:00
// Overwrite the mount type and mount path in the alias
// information
auth . Alias . MountType = req . MountType
auth . Alias . MountAccessor = req . MountAccessor
if auth . Alias . Name == "" {
return nil , nil , fmt . Errorf ( "missing name in alias" )
}
var err error
2018-02-09 15:40:56 +00:00
// Fetch the entity for the alias, or create an entity if one
// doesn't exist.
entity , err = c . identityStore . CreateOrFetchEntity ( auth . Alias )
2017-10-11 17:21:20 +00:00
if err != nil {
return nil , nil , err
}
if entity == nil {
2018-02-09 15:40:56 +00:00
return nil , nil , fmt . Errorf ( "failed to create an entity for the authenticated alias" )
2017-10-11 17:21:20 +00:00
}
2018-04-14 01:49:40 +00:00
if entity . Disabled {
2018-04-23 20:50:04 +00:00
return nil , nil , logical . ErrPermissionDenied
2018-04-14 01:49:40 +00:00
}
2017-10-11 17:21:20 +00:00
auth . EntityID = entity . ID
2017-11-02 20:05:48 +00:00
if auth . GroupAliases != nil {
err = c . identityStore . refreshExternalGroupMembershipsByEntityID ( auth . EntityID , auth . GroupAliases )
if err != nil {
return nil , nil , err
}
}
2017-10-11 17:21:20 +00:00
}
2016-08-08 21:32:37 +00:00
if strutil . StrListSubset ( auth . Policies , [ ] string { "root" } ) {
2017-09-13 01:48:52 +00:00
return logical . ErrorResponse ( "auth methods cannot create root tokens" ) , nil , logical . ErrInvalidRequest
2016-08-08 21:32:37 +00:00
}
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 {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
}
2018-04-03 16:20:20 +00:00
tokenTTL , warnings , err := framework . CalculateTTL ( sysView , 0 , auth . TTL , auth . Period , auth . MaxTTL , auth . ExplicitMaxTTL , time . Time { } )
if err != nil {
return nil , nil , err
}
for _ , warning := range warnings {
resp . AddWarning ( warning )
2016-05-02 04:08:07 +00:00
}
// Generate a token
te := TokenEntry {
Path : req . Path ,
Policies : auth . Policies ,
Meta : auth . Metadata ,
DisplayName : auth . DisplayName ,
CreationTime : time . Now ( ) . Unix ( ) ,
2017-12-15 18:30:05 +00:00
TTL : tokenTTL ,
2017-03-03 14:31:20 +00:00
NumUses : auth . NumUses ,
2017-10-18 17:23:05 +00:00
EntityID : auth . EntityID ,
2018-05-09 22:39:55 +00:00
BoundCIDRs : auth . BoundCIDRs ,
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
}
}
2018-01-19 06:44:44 +00:00
if err := c . tokenStore . create ( ctx , & te ) ; err != nil {
2018-04-03 00:46:59 +00:00
c . logger . Error ( "failed to create token" , "error" , err )
2016-05-02 04:08:07 +00:00
return nil , auth , ErrInternalError
}
2017-12-15 18:30:05 +00:00
// Populate the client token, accessor, and TTL
2016-05-02 04:08:07 +00:00
auth . ClientToken = te . ID
auth . Accessor = te . Accessor
auth . Policies = te . Policies
2017-12-15 18:30:05 +00:00
auth . TTL = te . TTL
2016-05-02 04:08:07 +00:00
// Register with the expiration manager
if err := c . expiration . RegisterAuth ( te . Path , auth ) ; err != nil {
2018-05-10 19:50:02 +00:00
c . tokenStore . revokeOrphan ( ctx , te . ID )
2018-04-03 00:46:59 +00:00
c . logger . Error ( "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
}