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"
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"
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"
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"
2019-01-09 00:48:57 +00:00
assetfs "github.com/elazarl/go-bindata-assetfs"
2016-03-08 23:27:03 +00:00
"github.com/hashicorp/errwrap"
2020-02-07 08:30:25 +00:00
cleanhttp "github.com/hashicorp/go-cleanhttp"
sockaddr "github.com/hashicorp/go-sockaddr"
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/helper/namespace"
2020-05-14 13:19:27 +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/parseutil"
"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"
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" ,
"/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" ,
}
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" ,
} )
}
2015-03-12 06:05:16 +00:00
// Handler returns an http.Handler for the API. This can be used on
// its own to mount the Vault API within another web server.
2018-07-06 19:44:56 +00:00
func Handler ( props * vault . HandlerProperties ) http . Handler {
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/pprof/" , handleLogicalNoForward ( core ) )
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 ) ) )
mux . Handle ( "/v1/sys/storage/raft/join" , handleSysRaftJoin ( core ) )
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 ) ) )
if core . UIEnabled ( ) == true {
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
}
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 )
2015-04-03 05:21:33 +00:00
2018-09-18 03:03:00 +00:00
genericWrappedHandler := genericWrapping ( core , corsWrappedHandler , 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 ) {
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 )
}
input := & logical . LogInput {
Request : req ,
}
core . AuditLogger ( ) . AuditRequest ( r . Context ( ) , input )
cw := newCopyResponseWriter ( w )
h . ServeHTTP ( cw , r )
data := make ( map [ string ] interface { } )
err = jsonutil . DecodeJSON ( cw . body . Bytes ( ) , & data )
if err != nil {
// best effort, ignore
}
httpResp := & logical . HTTPResponse { Data : data , Headers : cw . Header ( ) }
input . Response = logical . HTTPResponseToLogicalResponse ( httpResp )
core . AuditLogger ( ) . AuditResponse ( r . Context ( ) , input )
return
} )
}
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
}
2016-12-15 22:53:07 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Set the Cache-Control header for all the responses returned
// by Vault
w . 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
2020-05-21 20:07:50 +00:00
// Add our timeout, but not for the monitor endpoint, as it's streaming
if strings . HasSuffix ( r . URL . Path , "sys/monitor" ) {
ctx , cancelFunc = context . WithCancel ( ctx )
} else {
ctx , cancelFunc = context . WithTimeout ( ctx , maxRequestDuration )
}
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 )
2018-09-18 03:03:00 +00:00
switch {
case strings . HasPrefix ( r . URL . Path , "/v1/" ) :
newR , status := adjustRequest ( core , r )
if status != 0 {
respondError ( w , status , nil )
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 :
respondError ( w , http . StatusNotFound , nil )
cancelFunc ( )
return
}
2018-07-24 21:50:49 +00:00
h . ServeHTTP ( w , r )
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
}
respondError ( w , http . StatusBadRequest , errwrap . Wrapf ( "error parsing client hostport: {{err}}" , err ) )
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
}
respondError ( w , http . StatusBadRequest , errwrap . Wrapf ( "error parsing client address: {{err}}" , err ) )
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
} )
}
2016-09-29 04:01:28 +00:00
// A lookup on a token that is about to expire returns nil, which means by the
// time we can validate a wrapping token lookup will return nil since it will
// be revoked after the call. So we have to do the validation here.
2018-09-18 03:03:00 +00:00
func wrappingVerificationFunc ( ctx context . Context , core * vault . Core , req * logical . Request ) error {
2016-09-29 04:01:28 +00:00
if req == nil {
2016-03-18 03:01:28 +00:00
return fmt . Errorf ( "invalid request" )
}
2016-09-29 04:01:28 +00:00
2018-09-18 03:03:00 +00:00
valid , err := core . ValidateWrappingToken ( ctx , req )
2016-09-29 04:01:28 +00:00
if err != nil {
2018-04-05 15:49:21 +00:00
return errwrap . Wrapf ( "error validating wrapping token: {{err}}" , err )
2016-09-29 04:01:28 +00:00
}
if ! valid {
2019-07-05 21:15:14 +00:00
return consts . ErrInvalidWrappingToken
2016-09-29 04:01:28 +00:00
}
2016-03-18 02:29:53 +00:00
return nil
}
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 {
FileSystem * assetfs . AssetFS
}
func ( fs * UIAssetWrapper ) Open ( name string ) ( http . File , error ) {
file , err := fs . FileSystem . Open ( name )
if err == nil {
return file , nil
}
// serve index.html instead of 404ing
if err == os . ErrNotExist {
return fs . FileSystem . Open ( "index.html" )
}
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 {
reader = http . MaxBytesReader ( w , r . Body , max )
}
}
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 {
2019-04-05 18:36:34 +00:00
return nil , errwrap . Wrapf ( "failed to parse JSON input: {{err}}" , err )
}
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
}
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 ) {
2018-09-18 03:03:00 +00:00
// If we are a performance standby we can handle the request.
if core . PerfStandby ( ) {
ns , err := namespace . FromContext ( r . Context ( ) )
if err != nil {
respondError ( w , http . StatusBadRequest , err )
return
}
path := ns . TrimmedPath ( r . URL . Path [ len ( "/v1/" ) : ] )
2018-10-15 16:56:24 +00:00
switch {
2019-06-20 19:14:58 +00:00
case ! perfStandbyAlwaysForwardPaths . HasPath ( path ) && ! alwaysRedirectPaths . HasPath ( path ) :
2018-09-18 03:03:00 +00:00
handler . ServeHTTP ( w , r )
return
2018-10-15 16:56:24 +00:00
case strings . HasPrefix ( path , "auth/token/create/" ) :
isBatch , err := core . IsBatchTokenCreationRequest ( r . Context ( ) , path )
if err == nil && isBatch {
handler . ServeHTTP ( w , r )
return
}
2018-09-18 03:03:00 +00:00
}
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.
2018-09-18 03:03:00 +00:00
func requestAuth ( core * vault . Core , r * http . Request , req * logical . Request ) ( * logical . Request , error ) {
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
// Also attach the accessor if we have it. This doesn't fail if it
// doesn't exist because the request may be to an unauthenticated
// endpoint/login endpoint where a bad current token doesn't matter, or
2019-04-04 21:13:09 +00:00
// a token from a Vault version pre-accessors. We ignore errors for
// JWTs.
2018-10-01 17:33:21 +00:00
te , err := core . LookupToken ( r . Context ( ) , token )
2019-04-04 21:13:09 +00:00
if err != nil {
dotCount := strings . Count ( token , "." )
// If we have two dots but the second char is a dot it's a vault
// token of the form s.SOMETHING.nsid, not a JWT
if dotCount != 2 ||
dotCount == 2 && token [ 1 ] == '.' {
return req , err
}
2018-09-18 03:03:00 +00:00
}
2016-10-29 21:01:49 +00:00
if err == nil && te != nil {
req . ClientTokenAccessor = te . Accessor
2017-03-08 22:36:50 +00:00
req . ClientTokenRemainingUses = te . NumUses
2018-06-08 21:24:27 +00:00
req . SetTokenEntry ( te )
2016-10-29 21:01:49 +00:00
}
2015-05-11 17:56:41 +00:00
}
2018-09-18 03:03:00 +00:00
return req , nil
}
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 ] == "" {
return fmt . Errorf ( "invalid data in header %q; missing method name" , MFAHeaderName )
}
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:
//
// 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.
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
}
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
}
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 )
}
}