2015-03-12 06:05:16 +00:00
package http
import (
2019-04-05 18:36:34 +00:00
"bytes"
2018-07-06 19:44:56 +00:00
"context"
2015-03-12 06:05:16 +00:00
"encoding/json"
2018-07-06 19:44:56 +00:00
"errors"
2015-04-01 04:24:20 +00:00
"fmt"
2015-08-26 14:03:33 +00:00
"io"
2021-08-18 15:05:11 +00:00
"io/fs"
2019-04-05 18:36:34 +00:00
"io/ioutil"
2020-02-12 22:20:22 +00:00
"mime"
2018-04-17 22:52:09 +00:00
"net"
2015-03-12 06:05:16 +00:00
"net/http"
2021-04-19 18:30:59 +00:00
"net/http/pprof"
2018-04-17 22:52:09 +00:00
"net/textproto"
2015-04-19 20:18:09 +00:00
"net/url"
2018-03-27 20:23:33 +00:00
"os"
2021-10-15 18:22:44 +00:00
"regexp"
2015-04-03 05:21:33 +00:00
"strings"
2017-10-23 21:41:44 +00:00
"time"
2015-03-12 06:05:16 +00:00
2019-11-07 16:54:34 +00:00
"github.com/NYTimes/gziphandler"
2016-03-08 23:27:03 +00:00
"github.com/hashicorp/errwrap"
2021-04-20 22:25:04 +00:00
"github.com/hashicorp/go-cleanhttp"
2021-07-16 00:17:31 +00:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2021-04-20 22:25:04 +00:00
"github.com/hashicorp/go-sockaddr"
2021-12-08 22:34:42 +00:00
"github.com/hashicorp/go-uuid"
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/helper/namespace"
2020-10-13 23:38:21 +00:00
"github.com/hashicorp/vault/internalshared/configutil"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/pathmanager"
"github.com/hashicorp/vault/sdk/logical"
2015-03-12 06:05:16 +00:00
"github.com/hashicorp/vault/vault"
)
2016-05-02 02:39:45 +00:00
const (
2017-01-04 21:44:03 +00:00
// WrapTTLHeaderName is the name of the header containing a directive to
// wrap the response
2016-05-02 04:08:07 +00:00
WrapTTLHeaderName = "X-Vault-Wrap-TTL"
2016-08-15 13:42:42 +00:00
2017-01-04 21:44:03 +00:00
// WrapFormatHeaderName is the name of the header containing the format to
// wrap in; has no effect if the wrap TTL is not set
WrapFormatHeaderName = "X-Vault-Wrap-Format"
2016-08-15 13:42:42 +00:00
// NoRequestForwardingHeaderName is the name of the header telling Vault
// not to use request forwarding
NoRequestForwardingHeaderName = "X-Vault-No-Request-Forwarding"
2016-11-17 20:06:43 +00:00
2017-10-23 21:39:21 +00:00
// MFAHeaderName represents the HTTP header which carries the credentials
// required to perform MFA on any path.
MFAHeaderName = "X-Vault-MFA"
// canonicalMFAHeaderName is the MFA header value's format in the request
// headers. Do not alter the casing of this string.
canonicalMFAHeaderName = "X-Vault-Mfa"
// PolicyOverrideHeaderName is the header set to request overriding
// soft-mandatory Sentinel policies.
PolicyOverrideHeaderName = "X-Vault-Policy-Override"
2021-02-24 11:58:10 +00:00
VaultIndexHeaderName = "X-Vault-Index"
VaultInconsistentHeaderName = "X-Vault-Inconsistent"
VaultForwardHeaderName = "X-Vault-Forward"
VaultInconsistentForward = "forward-active-node"
VaultInconsistentFail = "fail"
2018-07-06 19:44:56 +00:00
// DefaultMaxRequestSize is the default maximum accepted request size. This
// is to prevent a denial of service attack where no Content-Length is
// provided and the server is fed ever more data until it exhausts memory.
// Can be overridden per listener.
DefaultMaxRequestSize = 32 * 1024 * 1024
2016-05-02 02:39:45 +00:00
)
2015-05-11 17:56:41 +00:00
2017-10-23 21:39:21 +00:00
var (
2018-03-27 20:23:33 +00:00
// Set to false by stub_asset if the ui build tag isn't enabled
uiBuiltIn = true
2018-09-05 15:45:17 +00:00
2018-09-18 03:03:00 +00:00
// perfStandbyAlwaysForwardPaths is used to check a requested path against
// the always forward list
perfStandbyAlwaysForwardPaths = pathmanager . New ( )
2019-06-20 19:14:58 +00:00
alwaysRedirectPaths = pathmanager . New ( )
2018-09-18 03:03:00 +00:00
2018-09-05 15:45:17 +00:00
injectDataIntoTopRoutes = [ ] string {
"/v1/sys/audit" ,
"/v1/sys/audit/" ,
"/v1/sys/audit-hash/" ,
"/v1/sys/auth" ,
"/v1/sys/auth/" ,
"/v1/sys/config/cors" ,
"/v1/sys/config/auditing/request-headers/" ,
"/v1/sys/config/auditing/request-headers" ,
"/v1/sys/capabilities" ,
"/v1/sys/capabilities-accessor" ,
"/v1/sys/capabilities-self" ,
2021-11-30 19:49:58 +00:00
"/v1/sys/ha-status" ,
2018-09-05 15:45:17 +00:00
"/v1/sys/key-status" ,
"/v1/sys/mounts" ,
"/v1/sys/mounts/" ,
"/v1/sys/policy" ,
"/v1/sys/policy/" ,
"/v1/sys/rekey/backup" ,
"/v1/sys/rekey/recovery-key-backup" ,
"/v1/sys/remount" ,
"/v1/sys/rotate" ,
"/v1/sys/wrapping/wrap" ,
}
2021-10-15 18:22:44 +00:00
oidcProtectedPathRegex = regexp . MustCompile ( ` ^identity/oidc/provider/\w(([\w-.]+)?\w)?/userinfo$ ` )
2017-10-23 21:39:21 +00:00
)
2019-06-20 19:14:58 +00:00
func init ( ) {
alwaysRedirectPaths . AddPaths ( [ ] string {
"sys/storage/raft/snapshot" ,
"sys/storage/raft/snapshot-force" ,
2021-08-16 17:12:00 +00:00
"!sys/storage/raft/snapshot-auto/config" ,
2019-06-20 19:14:58 +00:00
} )
}
2022-11-29 19:38:33 +00:00
type HandlerAnchor struct { }
func ( h HandlerAnchor ) Handler ( props * vault . HandlerProperties ) http . Handler {
return handler ( props )
}
var Handler vault . HandlerHandler = HandlerAnchor { }
type HandlerFunc func ( props * vault . HandlerProperties ) http . Handler
func ( h HandlerFunc ) Handler ( props * vault . HandlerProperties ) http . Handler {
return h ( props )
}
var _ vault . HandlerHandler = HandlerFunc ( func ( props * vault . HandlerProperties ) http . Handler { return nil } )
// handler returns an http.Handler for the API. This can be used on
2015-03-12 06:05:16 +00:00
// its own to mount the Vault API within another web server.
2022-11-29 19:38:33 +00:00
func handler ( props * vault . HandlerProperties ) http . Handler {
2018-07-06 19:44:56 +00:00
core := props . Core
2015-04-03 05:21:33 +00:00
// Create the muxer to handle the actual endpoints
2015-03-12 06:05:16 +00:00
mux := http . NewServeMux ( )
2019-09-19 20:44:37 +00:00
2019-10-15 04:55:31 +00:00
switch {
case props . RecoveryMode :
raw := vault . NewRawBackend ( core )
strategy := vault . GenerateRecoveryTokenStrategy ( props . RecoveryToken )
mux . Handle ( "/v1/sys/raw/" , handleLogicalRecovery ( raw , props . RecoveryToken ) )
mux . Handle ( "/v1/sys/generate-recovery-token/attempt" , handleSysGenerateRootAttempt ( core , strategy ) )
mux . Handle ( "/v1/sys/generate-recovery-token/update" , handleSysGenerateRootUpdate ( core , strategy ) )
default :
2019-10-15 15:42:51 +00:00
// Handle non-forwarded paths
mux . Handle ( "/v1/sys/config/state/" , handleLogicalNoForward ( core ) )
mux . Handle ( "/v1/sys/host-info" , handleLogicalNoForward ( core ) )
2019-10-15 04:55:31 +00:00
mux . Handle ( "/v1/sys/init" , handleSysInit ( core ) )
mux . Handle ( "/v1/sys/seal-status" , handleSysSealStatus ( core ) )
mux . Handle ( "/v1/sys/seal" , handleSysSeal ( core ) )
mux . Handle ( "/v1/sys/step-down" , handleRequestForwarding ( core , handleSysStepDown ( core ) ) )
mux . Handle ( "/v1/sys/unseal" , handleSysUnseal ( core ) )
mux . Handle ( "/v1/sys/leader" , handleSysLeader ( core ) )
mux . Handle ( "/v1/sys/health" , handleSysHealth ( core ) )
2020-05-21 20:07:50 +00:00
mux . Handle ( "/v1/sys/monitor" , handleLogicalNoForward ( core ) )
2020-02-06 16:56:37 +00:00
mux . Handle ( "/v1/sys/generate-root/attempt" , handleRequestForwarding ( core ,
handleAuditNonLogical ( core , handleSysGenerateRootAttempt ( core , vault . GenerateStandardRootTokenStrategy ) ) ) )
mux . Handle ( "/v1/sys/generate-root/update" , handleRequestForwarding ( core ,
handleAuditNonLogical ( core , handleSysGenerateRootUpdate ( core , vault . GenerateStandardRootTokenStrategy ) ) ) )
2019-10-15 04:55:31 +00:00
mux . Handle ( "/v1/sys/rekey/init" , handleRequestForwarding ( core , handleSysRekeyInit ( core , false ) ) )
mux . Handle ( "/v1/sys/rekey/update" , handleRequestForwarding ( core , handleSysRekeyUpdate ( core , false ) ) )
mux . Handle ( "/v1/sys/rekey/verify" , handleRequestForwarding ( core , handleSysRekeyVerify ( 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/rekey-recovery-key/verify" , handleRequestForwarding ( core , handleSysRekeyVerify ( core , true ) ) )
2020-06-23 19:04:13 +00:00
mux . Handle ( "/v1/sys/storage/raft/bootstrap" , handleSysRaftBootstrap ( core ) )
2019-10-15 04:55:31 +00:00
mux . Handle ( "/v1/sys/storage/raft/join" , handleSysRaftJoin ( core ) )
2021-01-06 22:05:00 +00:00
mux . Handle ( "/v1/sys/internal/ui/feature-flags" , handleSysInternalFeatureFlags ( core ) )
2023-02-09 21:18:58 +00:00
2019-10-15 04:55:31 +00:00
for _ , path := range injectDataIntoTopRoutes {
mux . Handle ( path , handleRequestForwarding ( core , handleLogicalWithInjector ( core ) ) )
2018-03-27 20:23:33 +00:00
}
2019-10-15 04:55:31 +00:00
mux . Handle ( "/v1/sys/" , handleRequestForwarding ( core , handleLogical ( core ) ) )
mux . Handle ( "/v1/" , handleRequestForwarding ( core , handleLogical ( core ) ) )
2022-02-18 14:35:53 +00:00
if core . UIEnabled ( ) {
2019-10-15 04:55:31 +00:00
if uiBuiltIn {
mux . Handle ( "/ui/" , http . StripPrefix ( "/ui/" , gziphandler . GzipHandler ( handleUIHeaders ( core , handleUI ( http . FileServer ( & UIAssetWrapper { FileSystem : assetFS ( ) } ) ) ) ) ) )
mux . Handle ( "/robots.txt" , gziphandler . GzipHandler ( handleUIHeaders ( core , handleUI ( http . FileServer ( & UIAssetWrapper { FileSystem : assetFS ( ) } ) ) ) ) )
} else {
mux . Handle ( "/ui/" , handleUIHeaders ( core , handleUIStub ( ) ) )
}
mux . Handle ( "/ui" , handleUIRedirect ( ) )
mux . Handle ( "/" , handleUIRedirect ( ) )
2015-04-03 05:21:33 +00:00
2019-10-15 04:55:31 +00:00
}
2019-10-04 07:29:51 +00:00
2019-10-15 04:55:31 +00:00
// Register metrics path without authentication if enabled
2020-05-14 13:19:27 +00:00
if props . ListenerConfig != nil && props . ListenerConfig . Telemetry . UnauthenticatedMetricsAccess {
2019-10-15 04:55:31 +00:00
mux . Handle ( "/v1/sys/metrics" , handleMetricsUnauthenticated ( core ) )
2020-02-07 08:30:25 +00:00
} else {
mux . Handle ( "/v1/sys/metrics" , handleLogicalNoForward ( core ) )
2019-10-15 04:55:31 +00:00
}
2021-04-19 18:30:59 +00:00
if props . ListenerConfig != nil && props . ListenerConfig . Profiling . UnauthenticatedPProfAccess {
for _ , name := range [ ] string { "goroutine" , "threadcreate" , "heap" , "allocs" , "block" , "mutex" } {
mux . Handle ( "/v1/sys/pprof/" + name , pprof . Handler ( name ) )
}
mux . Handle ( "/v1/sys/pprof/" , http . HandlerFunc ( pprof . Index ) )
mux . Handle ( "/v1/sys/pprof/cmdline" , http . HandlerFunc ( pprof . Cmdline ) )
mux . Handle ( "/v1/sys/pprof/profile" , http . HandlerFunc ( pprof . Profile ) )
mux . Handle ( "/v1/sys/pprof/symbol" , http . HandlerFunc ( pprof . Symbol ) )
mux . Handle ( "/v1/sys/pprof/trace" , http . HandlerFunc ( pprof . Trace ) )
} else {
mux . Handle ( "/v1/sys/pprof/" , handleLogicalNoForward ( core ) )
}
2021-12-08 22:34:42 +00:00
if props . ListenerConfig != nil && props . ListenerConfig . InFlightRequestLogging . UnauthenticatedInFlightAccess {
mux . Handle ( "/v1/sys/in-flight-req" , handleUnAuthenticatedInFlightRequest ( core ) )
} else {
mux . Handle ( "/v1/sys/in-flight-req" , handleLogicalNoForward ( core ) )
}
2019-10-15 04:55:31 +00:00
additionalRoutes ( mux , core )
}
2018-09-18 03:03:00 +00:00
2015-04-03 05:21:33 +00:00
// Wrap the handler in another handler to trigger all help paths.
2016-12-15 22:53:07 +00:00
helpWrappedHandler := wrapHelpHandler ( mux , core )
2017-06-17 04:04:55 +00:00
corsWrappedHandler := wrapCORSHandler ( helpWrappedHandler , core )
2020-06-26 21:13:16 +00:00
quotaWrappedHandler := rateLimitQuotaWrapping ( corsWrappedHandler , core )
genericWrappedHandler := genericWrapping ( core , quotaWrappedHandler , props )
2016-12-15 22:53:07 +00:00
2017-12-16 01:19:37 +00:00
// Wrap the handler with PrintablePathCheckHandler to check for non-printable
// characters in the request path.
2018-07-12 20:29:36 +00:00
printablePathCheckHandler := genericWrappedHandler
if ! props . DisablePrintableCheck {
printablePathCheckHandler = cleanhttp . PrintablePathCheckHandler ( genericWrappedHandler , nil )
}
2017-12-16 01:19:37 +00:00
return printablePathCheckHandler
2016-12-15 22:53:07 +00:00
}
2020-02-06 16:56:37 +00:00
type copyResponseWriter struct {
wrapped http . ResponseWriter
statusCode int
body * bytes . Buffer
}
// newCopyResponseWriter returns an initialized newCopyResponseWriter
func newCopyResponseWriter ( wrapped http . ResponseWriter ) * copyResponseWriter {
w := & copyResponseWriter {
wrapped : wrapped ,
body : new ( bytes . Buffer ) ,
statusCode : 200 ,
}
return w
}
func ( w * copyResponseWriter ) Header ( ) http . Header {
return w . wrapped . Header ( )
}
func ( w * copyResponseWriter ) Write ( buf [ ] byte ) ( int , error ) {
w . body . Write ( buf )
return w . wrapped . Write ( buf )
}
func ( w * copyResponseWriter ) WriteHeader ( code int ) {
w . statusCode = code
w . wrapped . WriteHeader ( code )
}
func handleAuditNonLogical ( core * vault . Core , h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2020-07-07 04:05:28 +00:00
origBody := new ( bytes . Buffer )
reader := ioutil . NopCloser ( io . TeeReader ( r . Body , origBody ) )
r . Body = reader
req , _ , status , err := buildLogicalRequestNoAuth ( core . PerfStandby ( ) , w , r )
if err != nil || status != 0 {
respondError ( w , status , err )
return
}
if origBody != nil {
r . Body = ioutil . NopCloser ( origBody )
}
2020-02-06 16:56:37 +00:00
input := & logical . LogInput {
2020-07-07 04:05:28 +00:00
Request : req ,
2020-02-06 16:56:37 +00:00
}
2020-10-07 12:30:36 +00:00
err = core . AuditLogger ( ) . AuditRequest ( r . Context ( ) , input )
if err != nil {
respondError ( w , status , err )
return
}
2020-02-06 16:56:37 +00:00
cw := newCopyResponseWriter ( w )
h . ServeHTTP ( cw , r )
data := make ( map [ string ] interface { } )
2020-07-07 04:05:28 +00:00
err = jsonutil . DecodeJSON ( cw . body . Bytes ( ) , & data )
2020-02-06 16:56:37 +00:00
if err != nil {
// best effort, ignore
}
httpResp := & logical . HTTPResponse { Data : data , Headers : cw . Header ( ) }
input . Response = logical . HTTPResponseToLogicalResponse ( httpResp )
2020-10-07 12:30:36 +00:00
err = core . AuditLogger ( ) . AuditResponse ( r . Context ( ) , input )
if err != nil {
respondError ( w , status , err )
}
2020-02-06 16:56:37 +00:00
return
} )
2020-06-26 21:13:16 +00:00
}
2020-02-06 16:56:37 +00:00
2016-12-15 22:53:07 +00:00
// wrapGenericHandler wraps the handler with an extra layer of handler where
// tasks that should be commonly handled for all the requests and/or responses
// are performed.
2020-05-14 13:19:27 +00:00
func wrapGenericHandler ( core * vault . Core , h http . Handler , props * vault . HandlerProperties ) http . Handler {
var maxRequestDuration time . Duration
var maxRequestSize int64
if props . ListenerConfig != nil {
maxRequestDuration = props . ListenerConfig . MaxRequestDuration
maxRequestSize = props . ListenerConfig . MaxRequestSize
}
2018-07-24 21:50:49 +00:00
if maxRequestDuration == 0 {
maxRequestDuration = vault . DefaultMaxRequestDuration
}
2020-05-14 13:19:27 +00:00
if maxRequestSize == 0 {
maxRequestSize = DefaultMaxRequestSize
}
2021-04-20 22:25:04 +00:00
// Swallow this error since we don't want to pollute the logs and we also don't want to
// return an HTTP error here. This information is best effort.
hostname , _ := os . Hostname ( )
2016-12-15 22:53:07 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2021-10-13 15:06:33 +00:00
// This block needs to be here so that upon sending SIGHUP, custom response
// headers are also reloaded into the handlers.
2021-11-23 19:30:25 +00:00
var customHeaders map [ string ] [ ] * logical . CustomHeader
2021-10-13 15:06:33 +00:00
if props . ListenerConfig != nil {
la := props . ListenerConfig . Address
listenerCustomHeaders := core . GetListenerCustomResponseHeaders ( la )
if listenerCustomHeaders != nil {
2021-11-23 19:30:25 +00:00
customHeaders = listenerCustomHeaders . StatusCodeHeaderMap
2021-10-13 15:06:33 +00:00
}
}
2021-12-08 22:34:42 +00:00
// saving start time for the in-flight requests
inFlightReqStartTime := time . Now ( )
2021-10-13 15:06:33 +00:00
2021-11-23 19:30:25 +00:00
nw := logical . NewStatusHeaderResponseWriter ( w , customHeaders )
2021-12-08 22:34:42 +00:00
2016-12-15 22:53:07 +00:00
// Set the Cache-Control header for all the responses returned
// by Vault
2021-11-23 19:30:25 +00:00
nw . Header ( ) . Set ( "Cache-Control" , "no-store" )
2018-07-06 19:44:56 +00:00
2018-07-24 21:50:49 +00:00
// Start with the request context
ctx := r . Context ( )
var cancelFunc context . CancelFunc
2023-02-09 21:18:58 +00:00
// Add our timeout, but not for the monitor or events endpoints, as they are streaming
if strings . HasSuffix ( r . URL . Path , "sys/monitor" ) || strings . Contains ( r . URL . Path , "sys/events" ) {
2020-05-21 20:07:50 +00:00
ctx , cancelFunc = context . WithCancel ( ctx )
} else {
ctx , cancelFunc = context . WithTimeout ( ctx , maxRequestDuration )
}
2021-01-19 19:28:28 +00:00
// if maxRequestSize < 0, no need to set context value
2018-07-24 21:50:49 +00:00
// Add a size limiter if desired
2018-07-06 19:44:56 +00:00
if maxRequestSize > 0 {
2018-07-24 21:50:49 +00:00
ctx = context . WithValue ( ctx , "max_request_size" , maxRequestSize )
2018-07-06 19:44:56 +00:00
}
2018-09-18 03:03:00 +00:00
ctx = context . WithValue ( ctx , "original_request_path" , r . URL . Path )
2018-07-24 21:50:49 +00:00
r = r . WithContext ( ctx )
2020-06-26 21:13:16 +00:00
r = r . WithContext ( namespace . ContextWithNamespace ( r . Context ( ) , namespace . RootNamespace ) )
2018-09-18 03:03:00 +00:00
2021-04-20 22:25:04 +00:00
// Set some response headers with raft node id (if applicable) and hostname, if available
if core . RaftNodeIDHeaderEnabled ( ) {
nodeID := core . GetRaftNodeID ( )
if nodeID != "" {
2021-11-23 19:30:25 +00:00
nw . Header ( ) . Set ( "X-Vault-Raft-Node-ID" , nodeID )
2021-04-20 22:25:04 +00:00
}
}
if core . HostnameHeaderEnabled ( ) && hostname != "" {
2021-11-23 19:30:25 +00:00
nw . Header ( ) . Set ( "X-Vault-Hostname" , hostname )
2021-04-20 22:25:04 +00:00
}
2018-09-18 03:03:00 +00:00
switch {
case strings . HasPrefix ( r . URL . Path , "/v1/" ) :
newR , status := adjustRequest ( core , r )
if status != 0 {
2021-11-23 19:30:25 +00:00
respondError ( nw , status , nil )
2018-09-18 03:03:00 +00:00
cancelFunc ( )
return
}
r = newR
2018-11-05 21:48:46 +00:00
case strings . HasPrefix ( r . URL . Path , "/ui" ) , r . URL . Path == "/robots.txt" , r . URL . Path == "/" :
2018-09-18 03:03:00 +00:00
default :
2021-11-23 19:30:25 +00:00
respondError ( nw , http . StatusNotFound , nil )
2018-09-18 03:03:00 +00:00
cancelFunc ( )
return
}
2021-12-08 22:34:42 +00:00
// The uuid for the request is going to be generated when a logical
// request is generated. But, here we generate one to be able to track
// in-flight requests, and use that to update the req data with clientID
inFlightReqID , err := uuid . GenerateUUID ( )
if err != nil {
respondError ( nw , http . StatusInternalServerError , fmt . Errorf ( "failed to generate an identifier for the in-flight request" ) )
}
// adding an entry to the context to enable updating in-flight
// data with ClientID in the logical layer
r = r . WithContext ( context . WithValue ( r . Context ( ) , logical . CtxKeyInFlightRequestID { } , inFlightReqID ) )
// extracting the client address to be included in the in-flight request
var clientAddr string
headers := r . Header [ textproto . CanonicalMIMEHeaderKey ( "X-Forwarded-For" ) ]
if len ( headers ) == 0 {
clientAddr = r . RemoteAddr
} else {
clientAddr = headers [ 0 ]
}
// getting the request method
requestMethod := r . Method
// Storing the in-flight requests. Path should include namespace as well
core . StoreInFlightReqData (
inFlightReqID ,
2022-01-27 18:06:34 +00:00
vault . InFlightReqData {
StartTime : inFlightReqStartTime ,
ReqPath : r . URL . Path ,
2021-12-08 22:34:42 +00:00
ClientRemoteAddr : clientAddr ,
2022-01-27 18:06:34 +00:00
Method : requestMethod ,
2021-12-08 22:34:42 +00:00
} )
defer func ( ) {
// Not expecting this fail, so skipping the assertion check
core . FinalizeInFlightReqData ( inFlightReqID , nw . StatusCode )
} ( )
2021-07-30 16:32:05 +00:00
// Setting the namespace in the header to be included in the error message
ns := r . Header . Get ( consts . NamespaceHeaderName )
if ns != "" {
2021-11-23 19:30:25 +00:00
nw . Header ( ) . Set ( consts . NamespaceHeaderName , ns )
2021-07-30 16:32:05 +00:00
}
2021-11-23 19:30:25 +00:00
h . ServeHTTP ( nw , r )
2020-06-26 21:13:16 +00:00
2018-07-24 21:50:49 +00:00
cancelFunc ( )
2016-12-15 22:53:07 +00:00
return
} )
2015-04-03 05:21:33 +00:00
}
2020-05-14 13:19:27 +00:00
func WrapForwardedForHandler ( h http . Handler , l * configutil . Listener ) http . Handler {
rejectNotPresent := l . XForwardedForRejectNotPresent
hopSkips := l . XForwardedForHopSkips
authorizedAddrs := l . XForwardedForAuthorizedAddrs
rejectNotAuthz := l . XForwardedForRejectNotAuthorized
2018-04-17 22:52:09 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
headers , headersOK := r . Header [ textproto . CanonicalMIMEHeaderKey ( "X-Forwarded-For" ) ]
if ! headersOK || len ( headers ) == 0 {
if ! rejectNotPresent {
h . ServeHTTP ( w , r )
return
}
respondError ( w , http . StatusBadRequest , fmt . Errorf ( "missing x-forwarded-for header and configured to reject when not present" ) )
return
}
host , port , err := net . SplitHostPort ( r . RemoteAddr )
if err != nil {
// If not rejecting treat it like we just don't have a valid
// header because we can't do a comparison against an address we
// can't understand
if ! rejectNotPresent {
h . ServeHTTP ( w , r )
return
}
2021-04-26 17:33:48 +00:00
respondError ( w , http . StatusBadRequest , fmt . Errorf ( "error parsing client hostport: %w" , err ) )
2018-04-17 22:52:09 +00:00
return
}
addr , err := sockaddr . NewIPAddr ( host )
if err != nil {
// We treat this the same as the case above
if ! rejectNotPresent {
h . ServeHTTP ( w , r )
return
}
2021-04-26 17:33:48 +00:00
respondError ( w , http . StatusBadRequest , fmt . Errorf ( "error parsing client address: %w" , err ) )
2018-04-17 22:52:09 +00:00
return
}
var found bool
for _ , authz := range authorizedAddrs {
if authz . Contains ( addr ) {
found = true
break
}
}
if ! found {
// If we didn't find it and aren't configured to reject, simply
// don't trust it
2020-05-14 13:19:27 +00:00
if ! rejectNotAuthz {
2018-04-17 22:52:09 +00:00
h . ServeHTTP ( w , r )
return
}
respondError ( w , http . StatusBadRequest , fmt . Errorf ( "client address not authorized for x-forwarded-for and configured to reject connection" ) )
return
}
// At this point we have at least one value and it's authorized
// Split comma separated ones, which are common. This brings it in line
// to the multiple-header case.
var acc [ ] string
for _ , header := range headers {
vals := strings . Split ( header , "," )
for _ , v := range vals {
acc = append ( acc , strings . TrimSpace ( v ) )
}
}
2020-05-14 13:19:27 +00:00
indexToUse := int64 ( len ( acc ) ) - 1 - hopSkips
2018-04-17 22:52:09 +00:00
if indexToUse < 0 {
// This is likely an error in either configuration or other
// infrastructure. We could either deny the request, or we
// could simply not trust the value. Denying the request is
// "safer" since if this logic is configured at all there may
// be an assumption it can always be trusted. Given that we can
// deny accepting the request at all if it's not from an
// authorized address, if we're at this point the address is
// authorized (or we've turned off explicit rejection) and we
// should assume that what comes in should be properly
// formatted.
respondError ( w , http . StatusBadRequest , fmt . Errorf ( "malformed x-forwarded-for configuration or request, hops to skip (%d) would skip before earliest chain link (chain length %d)" , hopSkips , len ( headers ) ) )
return
}
r . RemoteAddr = net . JoinHostPort ( acc [ indexToUse ] , port )
h . ServeHTTP ( w , r )
return
} )
}
2015-04-03 05:21:33 +00:00
// stripPrefix is a helper to strip a prefix from the path. It will
// return false from the second return value if it the prefix doesn't exist.
func stripPrefix ( prefix , path string ) ( string , bool ) {
if ! strings . HasPrefix ( path , prefix ) {
return "" , false
}
path = path [ len ( prefix ) : ]
if path == "" {
return "" , false
}
return path , true
2015-03-12 06:05:16 +00:00
}
2018-03-27 20:23:33 +00:00
func handleUIHeaders ( core * vault . Core , h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
header := w . Header ( )
userHeaders , err := core . UIHeaders ( )
if err != nil {
respondError ( w , http . StatusInternalServerError , err )
return
}
if userHeaders != nil {
for k := range userHeaders {
v := userHeaders . Get ( k )
header . Set ( k , v )
}
}
h . ServeHTTP ( w , req )
} )
}
func handleUI ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
2018-05-31 19:52:43 +00:00
// The fileserver handler strips trailing slashes and does a redirect.
// We don't want the redirect to happen so we preemptively trim the slash
// here.
req . URL . Path = strings . TrimSuffix ( req . URL . Path , "/" )
2018-03-27 20:23:33 +00:00
h . ServeHTTP ( w , req )
return
} )
}
func handleUIStub ( ) http . Handler {
stubHTML := `
< ! DOCTYPE html >
< html >
2018-10-31 23:23:00 +00:00
< style >
body {
color : # 1 F2124 ;
font - family : system - ui , - apple - system , BlinkMacSystemFont , "Segoe UI" , "Roboto" , "Oxygen" , "Ubuntu" , "Cantarell" , "Fira Sans" , "Droid Sans" , "Helvetica Neue" , sans - serif ;
}
. wrapper {
display : flex ;
justify - content : center ;
align - items : center ;
height : 500 px ;
}
. content ul {
line - height : 1.5 ;
}
a {
color : # 1563 ff ;
text - decoration : none ;
}
. header {
display : flex ;
color : # 6 a7786 ;
align - items : center ;
}
. header svg {
padding - right : 12 px ;
}
. alert {
transform : scale ( 0.07 ) ;
fill : # 6 a7786 ;
}
h1 {
font - weight : 500 ;
}
p {
margin - top : 0 px ;
}
< / style >
< div class = "wrapper" >
< div class = "content" >
< div class = "header" >
< svg width = "36px" height = "36px" viewBox = "0 0 36 36" xmlns = "http://www.w3.org/2000/svg" >
< path class = "alert" d = "M476.7 422.2L270.1 72.7c-2.9-5-8.3-8.7-14.1-8.7-5.9 0-11.3 3.7-14.1 8.7L35.3 422.2c-2.8 5-4.8 13-1.9 17.9 2.9 4.9 8.2 7.9 14 7.9h417.1c5.8 0 11.1-3 14-7.9 3-4.9 1-13-1.8-17.9zM288 400h-64v-48h64v48zm0-80h-64V176h64v144z" / >
< / svg >
< h1 > Vault UI is not available in this binary . < / h1 >
< / div >
< p > To get Vault UI do one of the following : < / p >
2018-03-27 20:23:33 +00:00
< ul >
< li > < a href = "https://www.vaultproject.io/downloads.html" > Download an official release < / a > < / li >
2020-03-24 21:15:27 +00:00
< li > Run < code > make bin < / code > to create your own release binaries .
2018-03-27 20:23:33 +00:00
< li > Run < code > make dev - ui < / code > to create a development binary with the UI .
< / ul >
2018-10-31 23:23:00 +00:00
< / div >
< / div >
2018-03-27 20:23:33 +00:00
< / html >
`
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
w . Write ( [ ] byte ( stubHTML ) )
} )
}
2018-10-15 16:23:46 +00:00
func handleUIRedirect ( ) http . Handler {
2018-03-27 20:23:33 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
http . Redirect ( w , req , "/ui/" , 307 )
return
} )
}
type UIAssetWrapper struct {
2021-08-18 15:05:11 +00:00
FileSystem http . FileSystem
2018-03-27 20:23:33 +00:00
}
2021-08-18 15:05:11 +00:00
func ( fsw * UIAssetWrapper ) Open ( name string ) ( http . File , error ) {
file , err := fsw . FileSystem . Open ( name )
2018-03-27 20:23:33 +00:00
if err == nil {
return file , nil
}
// serve index.html instead of 404ing
2021-08-18 15:05:11 +00:00
if errors . Is ( err , fs . ErrNotExist ) {
file , err := fsw . FileSystem . Open ( "index.html" )
return file , err
2018-03-27 20:23:33 +00:00
}
return nil , err
}
2019-07-18 17:42:36 +00:00
func parseQuery ( values url . Values ) map [ string ] interface { } {
data := map [ string ] interface { } { }
for k , v := range values {
// Skip the help key as this is a reserved parameter
if k == "help" {
continue
}
switch {
case len ( v ) == 0 :
case len ( v ) == 1 :
data [ k ] = v [ 0 ]
default :
data [ k ] = v
}
}
if len ( data ) > 0 {
return data
}
return nil
}
2020-02-12 22:20:22 +00:00
func parseJSONRequest ( perfStandby bool , r * http . Request , w http . ResponseWriter , out interface { } ) ( io . ReadCloser , error ) {
2016-11-17 20:06:43 +00:00
// Limit the maximum number of bytes to MaxRequestSize to protect
// against an indefinite amount of data being read.
2018-07-06 19:44:56 +00:00
reader := r . Body
ctx := r . Context ( )
maxRequestSize := ctx . Value ( "max_request_size" )
if maxRequestSize != nil {
max , ok := maxRequestSize . ( int64 )
if ! ok {
2019-04-05 18:36:34 +00:00
return nil , errors . New ( "could not parse max_request_size from request context" )
2018-07-06 19:44:56 +00:00
}
if max > 0 {
2021-10-13 15:06:33 +00:00
// MaxBytesReader won't do all the internal stuff it must unless it's
// given a ResponseWriter that implements the internal http interface
// requestTooLarger. So we let it have access to the underlying
// ResponseWriter.
inw := w
2021-11-23 19:30:25 +00:00
if myw , ok := inw . ( logical . WrappingResponseWriter ) ; ok {
2021-10-13 15:06:33 +00:00
inw = myw . Wrapped ( )
}
reader = http . MaxBytesReader ( inw , r . Body , max )
2018-07-06 19:44:56 +00:00
}
}
2019-04-05 18:36:34 +00:00
var origBody io . ReadWriter
2019-10-15 04:55:31 +00:00
if perfStandby {
2019-04-05 18:36:34 +00:00
// Since we're checking PerfStandby here we key on origBody being nil
// or not later, so we need to always allocate so it's non-nil
origBody = new ( bytes . Buffer )
reader = ioutil . NopCloser ( io . TeeReader ( reader , origBody ) )
}
2018-07-06 19:44:56 +00:00
err := jsonutil . DecodeJSONFromReader ( reader , out )
2015-08-26 14:03:33 +00:00
if err != nil && err != io . EOF {
2021-04-26 17:33:48 +00:00
return nil , fmt . Errorf ( "failed to parse JSON input: %w" , err )
2019-04-05 18:36:34 +00:00
}
if origBody != nil {
return ioutil . NopCloser ( origBody ) , err
2015-08-26 14:03:33 +00:00
}
2019-04-05 18:36:34 +00:00
return nil , err
2015-03-12 06:05:16 +00:00
}
2020-02-12 22:20:22 +00:00
// parseFormRequest parses values from a form POST.
//
// A nil map will be returned if the format is empty or invalid.
func parseFormRequest ( r * http . Request ) ( map [ string ] interface { } , error ) {
maxRequestSize := r . Context ( ) . Value ( "max_request_size" )
if maxRequestSize != nil {
max , ok := maxRequestSize . ( int64 )
if ! ok {
return nil , errors . New ( "could not parse max_request_size from request context" )
}
if max > 0 {
r . Body = ioutil . NopCloser ( io . LimitReader ( r . Body , max ) )
}
}
if err := r . ParseForm ( ) ; err != nil {
return nil , err
}
var data map [ string ] interface { }
if len ( r . PostForm ) != 0 {
data = make ( map [ string ] interface { } , len ( r . PostForm ) )
for k , v := range r . PostForm {
switch len ( v ) {
case 0 :
case 1 :
data [ k ] = v [ 0 ]
default :
// Almost anywhere taking in a string list can take in comma
// separated values, and really this is super niche anyways
data [ k ] = strings . Join ( v , "," )
}
}
}
return data , nil
}
2021-02-24 11:58:10 +00:00
// forwardBasedOnHeaders returns true if the request headers specify that
// we should forward to the active node - either unconditionally or because
// a specified state isn't present locally.
func forwardBasedOnHeaders ( core * vault . Core , r * http . Request ) ( bool , error ) {
rawForward := r . Header . Get ( VaultForwardHeaderName )
if rawForward != "" {
if ! core . AllowForwardingViaHeader ( ) {
return false , fmt . Errorf ( "forwarding via header %s disabled in configuration" , VaultForwardHeaderName )
}
if rawForward == "active-node" {
return true , nil
}
return false , nil
}
rawInconsistent := r . Header . Get ( VaultInconsistentHeaderName )
if rawInconsistent == "" {
return false , nil
}
switch rawInconsistent {
case VaultInconsistentForward :
if ! core . AllowForwardingViaHeader ( ) {
return false , fmt . Errorf ( "forwarding via header %s=%s disabled in configuration" ,
VaultInconsistentHeaderName , VaultInconsistentForward )
}
default :
return false , nil
}
2021-06-11 17:18:16 +00:00
return core . MissingRequiredState ( r . Header . Values ( VaultIndexHeaderName ) , core . PerfStandby ( ) ) , nil
2021-02-24 11:58:10 +00:00
}
2016-08-15 13:42:42 +00:00
// handleRequestForwarding determines whether to forward a request or not,
// falling back on the older behavior of redirecting the client
func handleRequestForwarding ( core * vault . Core , handler http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2021-02-24 11:58:10 +00:00
// Note if the client requested forwarding
shouldForward , err := forwardBasedOnHeaders ( core , r )
if err != nil {
respondError ( w , http . StatusBadRequest , err )
return
}
// If we are a performance standby we can maybe handle the request.
if core . PerfStandby ( ) && ! shouldForward {
2018-09-18 03:03:00 +00:00
ns , err := namespace . FromContext ( r . Context ( ) )
if err != nil {
respondError ( w , http . StatusBadRequest , err )
return
}
path := ns . TrimmedPath ( r . URL . Path [ len ( "/v1/" ) : ] )
2023-01-24 19:00:27 +00:00
if ! perfStandbyAlwaysForwardPaths . HasPath ( path ) && ! alwaysRedirectPaths . HasPath ( path ) {
2018-09-18 03:03:00 +00:00
handler . ServeHTTP ( w , r )
return
}
2016-08-15 13:42:42 +00:00
}
// Note: in an HA setup, this call will also ensure that connections to
// the leader are set up, as that happens once the advertised cluster
// values are read during this function
2017-07-31 22:25:27 +00:00
isLeader , leaderAddr , _ , err := core . Leader ( )
2016-08-15 13:42:42 +00:00
if err != nil {
if err == vault . ErrHANotEnabled {
// Standalone node, serve request normally
handler . ServeHTTP ( w , r )
return
}
// Some internal error occurred
respondError ( w , http . StatusInternalServerError , err )
return
}
if isLeader {
// No forwarding needed, we're leader
handler . ServeHTTP ( w , r )
return
}
if leaderAddr == "" {
2017-07-31 22:25:27 +00:00
respondError ( w , http . StatusInternalServerError , fmt . Errorf ( "local node not active but active cluster node not found" ) )
2016-08-15 13:42:42 +00:00
return
}
2018-09-18 03:03:00 +00:00
forwardRequest ( core , w , r )
return
} )
}
2016-08-15 13:42:42 +00:00
2018-09-18 03:03:00 +00:00
func forwardRequest ( core * vault . Core , w http . ResponseWriter , r * http . Request ) {
if r . Header . Get ( vault . IntNoForwardingHeaderName ) != "" {
respondStandby ( core , w , r . URL )
return
}
2016-08-15 13:42:42 +00:00
2018-09-18 03:03:00 +00:00
if r . Header . Get ( NoRequestForwardingHeaderName ) != "" {
// Forwarding explicitly disabled, fall back to previous behavior
core . Logger ( ) . Debug ( "handleRequestForwarding: forwarding disabled by client request" )
respondStandby ( core , w , r . URL )
return
}
2019-06-20 19:14:58 +00:00
ns , err := namespace . FromContext ( r . Context ( ) )
if err != nil {
respondError ( w , http . StatusBadRequest , err )
return
}
path := ns . TrimmedPath ( r . URL . Path [ len ( "/v1/" ) : ] )
if alwaysRedirectPaths . HasPath ( path ) {
respondStandby ( core , w , r . URL )
return
}
2018-09-18 03:03:00 +00:00
// Attempt forwarding the request. If we cannot forward -- perhaps it's
// been disabled on the active node -- this will return with an
// ErrCannotForward and we simply fall back
statusCode , header , retBytes , err := core . ForwardRequest ( r )
if err != nil {
if err == vault . ErrCannotForward {
2018-10-09 16:43:17 +00:00
core . Logger ( ) . Debug ( "cannot forward request (possibly disabled on active node), falling back" )
2018-09-18 03:03:00 +00:00
} else {
2018-10-09 16:43:17 +00:00
core . Logger ( ) . Error ( "forward request error" , "error" , err )
2016-08-26 21:53:47 +00:00
}
2018-09-18 03:03:00 +00:00
// Fall back to redirection
respondStandby ( core , w , r . URL )
2016-08-15 13:42:42 +00:00
return
2018-09-18 03:03:00 +00:00
}
if header != nil {
for k , v := range header {
w . Header ( ) [ k ] = v
}
}
w . WriteHeader ( statusCode )
w . Write ( retBytes )
2016-08-15 13:42:42 +00:00
}
2015-04-08 18:19:03 +00:00
// request is a helper to perform a request and properly exit in the
// case of an error.
2019-01-25 19:08:42 +00:00
func request ( core * vault . Core , w http . ResponseWriter , rawReq * http . Request , r * logical . Request ) ( * logical . Response , bool , bool ) {
2018-07-24 21:50:49 +00:00
resp , err := core . HandleRequest ( rawReq . Context ( ) , r )
2018-09-18 03:03:00 +00:00
if r . LastRemoteWAL ( ) > 0 && ! vault . WaitUntilWALShipped ( rawReq . Context ( ) , core , r . LastRemoteWAL ( ) ) {
if resp == nil {
resp = & logical . Response { }
}
resp . AddWarning ( "Timeout hit while waiting for local replicated cluster to apply primary's write; this client may encounter stale reads of values written during this operation." )
}
2017-02-16 20:15:02 +00:00
if errwrap . Contains ( err , consts . ErrStandby . Error ( ) ) {
2015-04-19 21:36:50 +00:00
respondStandby ( core , w , rawReq . URL )
2019-01-25 19:08:42 +00:00
return resp , false , false
}
if err != nil && errwrap . Contains ( err , logical . ErrPerfStandbyPleaseForward . Error ( ) ) {
return nil , false , true
2015-04-19 20:18:09 +00:00
}
2018-09-18 20:26:06 +00:00
2019-02-05 21:02:15 +00:00
if resp != nil && len ( resp . Headers ) > 0 {
// Set this here so it will take effect regardless of any other type of
// response processing
header := w . Header ( )
for k , v := range resp . Headers {
for _ , h := range v {
header . Add ( k , h )
}
}
switch {
case resp . Secret != nil ,
resp . Auth != nil ,
len ( resp . Data ) > 0 ,
resp . Redirect != "" ,
len ( resp . Warnings ) > 0 ,
resp . WrapInfo != nil :
// Nothing, resp has data
default :
// We have an otherwise totally empty response except for headers,
// so nil out the response now that the headers are written out
resp = nil
}
}
2019-06-20 19:14:58 +00:00
// If vault's core has already written to the response writer do not add any
// additional output. Headers have already been sent. If the response writer
// is set but has not been written to it likely means there was some kind of
// error
if r . ResponseWriter != nil && r . ResponseWriter . Written ( ) {
return nil , true , false
}
2017-02-16 20:15:02 +00:00
if respondErrorCommon ( w , r , resp , err ) {
2019-01-25 19:08:42 +00:00
return resp , false , false
2015-04-08 18:19:03 +00:00
}
2019-01-25 19:08:42 +00:00
return resp , true , false
2015-04-08 18:19:03 +00:00
}
2015-04-19 20:18:09 +00:00
// respondStandby is used to trigger a redirect in the case that this Vault is currently a hot standby
func respondStandby ( core * vault . Core , w http . ResponseWriter , reqURL * url . URL ) {
// Request the leader address
2017-07-31 22:25:27 +00:00
_ , redirectAddr , _ , err := core . Leader ( )
2015-04-19 20:18:09 +00:00
if err != nil {
2018-07-06 21:01:19 +00:00
if err == vault . ErrHANotEnabled {
// Standalone node, serve 503
err = errors . New ( "node is not active" )
respondError ( w , http . StatusServiceUnavailable , err )
return
}
2015-04-19 20:18:09 +00:00
respondError ( w , http . StatusInternalServerError , err )
return
}
// If there is no leader, generate a 503 error
2016-08-15 13:42:42 +00:00
if redirectAddr == "" {
2018-07-06 21:01:19 +00:00
err = errors . New ( "no active Vault instance found" )
2015-04-19 20:18:09 +00:00
respondError ( w , http . StatusServiceUnavailable , err )
return
}
2016-08-15 13:42:42 +00:00
// Parse the redirect location
redirectURL , err := url . Parse ( redirectAddr )
2015-04-19 20:18:09 +00:00
if err != nil {
respondError ( w , http . StatusInternalServerError , err )
return
}
// Generate a redirect URL
2016-08-15 13:42:42 +00:00
finalURL := url . URL {
Scheme : redirectURL . Scheme ,
Host : redirectURL . Host ,
2015-04-19 20:18:09 +00:00
Path : reqURL . Path ,
RawQuery : reqURL . RawQuery ,
}
// Ensure there is a scheme, default to https
2016-08-15 13:42:42 +00:00
if finalURL . Scheme == "" {
finalURL . Scheme = "https"
2015-04-19 20:18:09 +00:00
}
// If we have an address, redirect! We use a 307 code
// because we don't actually know if its permanent and
// the request method should be preserved.
2016-08-15 13:42:42 +00:00
w . Header ( ) . Set ( "Location" , finalURL . String ( ) )
2015-04-19 20:18:09 +00:00
w . WriteHeader ( 307 )
}
2019-02-05 21:02:15 +00:00
// getTokenFromReq parse headers of the incoming request to extract token if
// present it accepts Authorization Bearer (RFC6750) and X-Vault-Token header.
// Returns true if the token was sourced from a Bearer header.
func getTokenFromReq ( r * http . Request ) ( string , bool ) {
2018-10-01 17:33:21 +00:00
if token := r . Header . Get ( consts . AuthHeaderName ) ; token != "" {
2019-02-05 21:02:15 +00:00
return token , false
2018-10-01 17:33:21 +00:00
}
2019-02-11 18:08:15 +00:00
if headers , ok := r . Header [ "Authorization" ] ; ok {
2018-10-01 17:33:21 +00:00
// Reference for Authorization header format: https://tools.ietf.org/html/rfc7236#section-3
2019-02-05 21:02:15 +00:00
// If string does not start by 'Bearer ', it is not one we would use,
// but might be used by plugins
2019-02-11 18:08:15 +00:00
for _ , v := range headers {
if ! strings . HasPrefix ( v , "Bearer " ) {
continue
}
return strings . TrimSpace ( v [ 7 : ] ) , true
2018-10-01 17:33:21 +00:00
}
}
2019-02-05 21:02:15 +00:00
return "" , false
2018-10-01 17:33:21 +00:00
}
2015-03-29 23:14:54 +00:00
// requestAuth adds the token to the logical.Request if it exists.
2021-06-11 17:18:16 +00:00
func requestAuth ( r * http . Request , req * logical . Request ) {
2015-05-11 17:56:41 +00:00
// Attach the header value if we have it
2019-02-05 21:02:15 +00:00
token , fromAuthzHeader := getTokenFromReq ( r )
if token != "" {
2018-10-01 17:33:21 +00:00
req . ClientToken = token
2019-02-11 18:08:15 +00:00
req . ClientTokenSource = logical . ClientTokenFromVaultHeader
if fromAuthzHeader {
req . ClientTokenSource = logical . ClientTokenFromAuthzHeader
}
2016-10-29 21:01:49 +00:00
2015-05-11 17:56:41 +00:00
}
2018-09-18 03:03:00 +00:00
}
func requestPolicyOverride ( r * http . Request , req * logical . Request ) error {
raw := r . Header . Get ( PolicyOverrideHeaderName )
if raw == "" {
return nil
}
override , err := parseutil . ParseBool ( raw )
if err != nil {
return err
}
req . PolicyOverride = override
return nil
2015-03-29 23:14:54 +00:00
}
2017-01-04 21:44:03 +00:00
// requestWrapInfo adds the WrapInfo value to the logical.Request if wrap info exists
func requestWrapInfo ( r * http . Request , req * logical . Request ) ( * logical . Request , error ) {
2016-05-02 02:39:45 +00:00
// First try for the header value
2016-05-02 04:08:07 +00:00
wrapTTL := r . Header . Get ( WrapTTLHeaderName )
if wrapTTL == "" {
2016-05-02 02:39:45 +00:00
return req , nil
}
// If it has an allowed suffix parse as a duration string
2017-03-07 16:21:22 +00:00
dur , err := parseutil . ParseDurationSecond ( wrapTTL )
2016-07-11 18:19:35 +00:00
if err != nil {
return req , err
2016-05-02 02:39:45 +00:00
}
2016-07-11 18:19:35 +00:00
if int64 ( dur ) < 0 {
2016-06-07 19:00:35 +00:00
return req , fmt . Errorf ( "requested wrap ttl cannot be negative" )
}
2017-01-04 21:44:03 +00:00
req . WrapInfo = & logical . RequestWrapInfo {
TTL : dur ,
}
wrapFormat := r . Header . Get ( WrapFormatHeaderName )
switch wrapFormat {
case "jwt" :
req . WrapInfo . Format = "jwt"
}
2016-05-02 02:39:45 +00:00
return req , nil
}
2018-09-18 03:03:00 +00:00
// parseMFAHeader parses the MFAHeaderName in the request headers and organizes
// them with MFA method name as the index.
func parseMFAHeader ( req * logical . Request ) error {
if req == nil {
return fmt . Errorf ( "request is nil" )
}
if req . Headers == nil {
return nil
}
// Reset and initialize the credentials in the request
req . MFACreds = make ( map [ string ] [ ] string )
for _ , mfaHeaderValue := range req . Headers [ canonicalMFAHeaderName ] {
// Skip the header with no value in it
if mfaHeaderValue == "" {
continue
}
// Handle the case where only method name is mentioned and no value
// is supplied
if ! strings . Contains ( mfaHeaderValue , ":" ) {
2019-03-19 13:32:45 +00:00
// Mark the presence of method name, but set an empty set to it
2018-09-18 03:03:00 +00:00
// indicating that there were no values supplied for the method
if req . MFACreds [ mfaHeaderValue ] == nil {
req . MFACreds [ mfaHeaderValue ] = [ ] string { }
}
continue
}
shardSplits := strings . SplitN ( mfaHeaderValue , ":" , 2 )
if shardSplits [ 0 ] == "" {
2022-02-17 21:08:51 +00:00
return fmt . Errorf ( "invalid data in header %q; missing method name or ID" , MFAHeaderName )
2018-09-18 03:03:00 +00:00
}
if shardSplits [ 1 ] == "" {
return fmt . Errorf ( "invalid data in header %q; missing method value" , MFAHeaderName )
}
req . MFACreds [ shardSplits [ 0 ] ] = append ( req . MFACreds [ shardSplits [ 0 ] ] , shardSplits [ 1 ] )
}
return nil
}
2020-02-12 22:20:22 +00:00
// isForm tries to determine whether the request should be
// processed as a form or as JSON.
//
// Virtually all existing use cases have assumed processing as JSON,
// and there has not been a Content-Type requirement in the API. In order to
// maintain backwards compatibility, this will err on the side of JSON.
// The request will be considered a form only if:
//
2022-09-08 00:31:20 +00:00
// 1. The content type is "application/x-www-form-urlencoded"
// 2. The start of the request doesn't look like JSON. For this test we
// we expect the body to begin with { or [, ignoring leading whitespace.
2020-02-12 22:20:22 +00:00
func isForm ( head [ ] byte , contentType string ) bool {
contentType , _ , err := mime . ParseMediaType ( contentType )
if err != nil || contentType != "application/x-www-form-urlencoded" {
return false
}
// Look for the start of JSON or not-JSON, skipping any insignificant
// whitespace (per https://tools.ietf.org/html/rfc7159#section-2).
for _ , c := range head {
switch c {
case ' ' , '\t' , '\n' , '\r' :
continue
case '[' , '{' : // JSON
return false
default : // not JSON
return true
}
}
return true
}
2015-03-12 06:05:16 +00:00
func respondError ( w http . ResponseWriter , status int , err error ) {
2019-03-05 16:43:30 +00:00
logical . RespondError ( w , status , err )
2015-03-12 06:05:16 +00:00
}
2022-10-17 18:46:25 +00:00
func respondErrorAndData ( w http . ResponseWriter , status int , data interface { } , err error ) {
logical . RespondErrorAndData ( w , status , data , err )
}
2017-02-16 20:15:02 +00:00
func respondErrorCommon ( w http . ResponseWriter , req * logical . Request , resp * logical . Response , err error ) bool {
statusCode , newErr := logical . RespondErrorCommon ( req , resp , err )
if newErr == nil && statusCode == 0 {
2015-04-01 04:29:53 +00:00
return false
}
2021-10-15 18:22:44 +00:00
// If ErrPermissionDenied occurs for OIDC protected resources (e.g., userinfo),
// then respond with a JSON error format that complies with the specification.
// This prevents the JSON error format from changing to a Vault-y format (i.e.,
// the format that results from respondError) after an OIDC access token expires.
if oidcPermissionDenied ( req . Path , err ) {
respondOIDCPermissionDenied ( w )
return true
}
2022-10-17 18:46:25 +00:00
if resp != nil {
if data := resp . Data [ "data" ] ; data != nil {
respondErrorAndData ( w , statusCode , data , newErr )
return true
}
}
2017-02-16 20:15:02 +00:00
respondError ( w , statusCode , newErr )
2016-06-22 21:47:05 +00:00
return true
2015-04-01 04:24:20 +00:00
}
2015-03-12 06:05:16 +00:00
func respondOk ( w http . ResponseWriter , body interface { } ) {
2017-11-02 12:31:50 +00:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2015-03-12 06:05:16 +00:00
if body == nil {
w . WriteHeader ( http . StatusNoContent )
} else {
w . WriteHeader ( http . StatusOK )
enc := json . NewEncoder ( w )
enc . Encode ( body )
}
}
2021-10-15 18:22:44 +00:00
// oidcPermissionDenied returns true if the given path matches the
// UserInfo Endpoint published by Vault OIDC providers and the given
// error is a logical.ErrPermissionDenied.
func oidcPermissionDenied ( path string , err error ) bool {
return errwrap . Contains ( err , logical . ErrPermissionDenied . Error ( ) ) &&
oidcProtectedPathRegex . MatchString ( path )
}
// respondOIDCPermissionDenied writes a response to the given w for
// permission denied errors (expired token) on resources protected
// by OIDC access tokens. Currently, the UserInfo Endpoint is the only
// protected resource. See the following specifications for details:
2022-09-08 00:31:20 +00:00
// - https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError
// - https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
2021-10-15 18:22:44 +00:00
func respondOIDCPermissionDenied ( w http . ResponseWriter ) {
errorCode := "invalid_token"
errorDescription := logical . ErrPermissionDenied . Error ( )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
w . Header ( ) . Set ( "WWW-Authenticate" , fmt . Sprintf ( "Bearer error=%q,error_description=%q" ,
errorCode , errorDescription ) )
w . WriteHeader ( http . StatusUnauthorized )
var oidcResponse struct {
Error string ` json:"error" `
ErrorDescription string ` json:"error_description" `
}
oidcResponse . Error = errorCode
oidcResponse . ErrorDescription = errorDescription
enc := json . NewEncoder ( w )
enc . Encode ( oidcResponse )
}