blob: 3b91ed3320e2281e85e3e6995440becda8afb8f5 [file] [log] [blame]
// Copyright 2017 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package pprof is similar to net/http/pprof, except it supports auth.
//
// Use it instead of net/http/pprof in LUCI server environments.
//
// It uses temporary HMAC-based tokens (generated through admin portal) for
// authenticating requests. Requires a secret store to be installed in the
// base middleware.
package pprof
import (
"fmt"
"html"
"net/http"
"go.chromium.org/luci/common/retry/transient"
"go.chromium.org/luci/server/router"
"go.chromium.org/luci/server/pprof/internal"
)
var pprofRoutes = map[string]http.HandlerFunc{
"cmdline": internal.Cmdline,
"profile": internal.Profile,
"symbol": internal.Symbol,
"trace": internal.Trace,
}
// InstallHandlers installs HTTP handlers for pprof routes.
func InstallHandlers(r *router.Router, base router.MiddlewareChain) {
// Pprof native routing structure is not supported by julienschmidt/httprouter
// since it mixes prefix matches and direct matches. So we'll have to do some
// manual routing for paths under /debug/pprof :(
r.GET("/debug/pprof/*path", base, func(c *router.Context) {
// Validate the token generated through the portal page.
tok := c.Request.FormValue("tok")
if tok == "" {
sendError(c.Writer, "Missing 'tok' query parameter with a pprof token", http.StatusBadRequest)
return
}
switch err := checkToken(c.Context, tok); {
case transient.Tag.In(err):
sendError(c.Writer, fmt.Sprintf("Transient error, please retry: %s", err), http.StatusInternalServerError)
return
case err != nil:
sendError(c.Writer, fmt.Sprintf("Bad pprof token: %s", err), http.StatusBadRequest)
return
}
// Manually route. See init() in go/src/net/http/pprof/pprof.go.
h := pprofRoutes[c.Params.ByName("path")]
if h == nil {
h = internal.Index
}
h(c.Writer, c.Request)
})
}
const errorPage = `<html>
<head>
<title>/debug/pprof/</title>
</head>
<body>
<h2>Error</h2>
<p>%s.</p>
<hr>
<p>To generate a new pprof token visit <a href="/admin/portal/pprof">the admin
portal</a> and follow instructions there.</p>
</body>
</html>
`
func sendError(w http.ResponseWriter, msg string, code int) {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(code)
w.Write([]byte(fmt.Sprintf(errorPage, html.EscapeString(msg))))
}