debian-mirror-gitlab/workhorse/internal/upstream/upstream.go

278 lines
7.7 KiB
Go
Raw Normal View History

2021-02-22 17:27:13 +05:30
/*
The upstream type implements http.Handler.
In this file we handle request routing and interaction with the authBackend.
*/
package upstream
import (
"fmt"
2021-09-30 23:02:18 +05:30
"os"
2021-10-27 15:23:28 +05:30
"sync"
"time"
2021-02-22 17:27:13 +05:30
"net/http"
2021-10-27 15:23:28 +05:30
"net/url"
2021-02-22 17:27:13 +05:30
"strings"
"github.com/sirupsen/logrus"
2021-10-27 15:23:28 +05:30
2021-02-22 17:27:13 +05:30
"gitlab.com/gitlab-org/labkit/correlation"
2021-10-27 15:23:28 +05:30
apipkg "gitlab.com/gitlab-org/gitlab/workhorse/internal/api"
2022-10-11 01:57:18 +05:30
"gitlab.com/gitlab-org/gitlab/workhorse/internal/builds"
2021-10-27 15:23:28 +05:30
"gitlab.com/gitlab-org/gitlab/workhorse/internal/config"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/helper"
proxypkg "gitlab.com/gitlab-org/gitlab/workhorse/internal/proxy"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/rejectmethods"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/upload"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/upstream/roundtripper"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/urlprefix"
2021-02-22 17:27:13 +05:30
)
var (
DefaultBackend = helper.URLMustParse("http://localhost:8080")
requestHeaderBlacklist = []string{
upload.RewrittenFieldsHeader,
}
2021-10-27 15:23:28 +05:30
geoProxyApiPollingInterval = 10 * time.Second
2021-02-22 17:27:13 +05:30
)
type upstream struct {
config.Config
2021-09-30 23:02:18 +05:30
URLPrefix urlprefix.Prefix
Routes []routeEntry
RoundTripper http.RoundTripper
CableRoundTripper http.RoundTripper
APIClient *apipkg.API
2021-10-27 15:23:28 +05:30
geoProxyBackend *url.URL
2022-06-21 17:19:12 +05:30
geoProxyExtraData string
2021-10-27 15:23:28 +05:30
geoLocalRoutes []routeEntry
geoProxyCableRoute routeEntry
geoProxyRoute routeEntry
2021-11-18 22:05:49 +05:30
geoProxyPollSleep func(time.Duration)
2022-08-13 15:12:31 +05:30
geoPollerDone chan struct{}
2021-09-30 23:02:18 +05:30
accessLogger *logrus.Logger
enableGeoProxyFeature bool
2021-10-27 15:23:28 +05:30
mu sync.RWMutex
2022-10-11 01:57:18 +05:30
watchKeyHandler builds.WatchKeyHandler
2021-02-22 17:27:13 +05:30
}
2022-10-11 01:57:18 +05:30
func NewUpstream(cfg config.Config, accessLogger *logrus.Logger, watchKeyHandler builds.WatchKeyHandler) http.Handler {
return newUpstream(cfg, accessLogger, configureRoutes, watchKeyHandler)
2021-03-05 16:19:46 +05:30
}
2022-10-11 01:57:18 +05:30
func newUpstream(cfg config.Config, accessLogger *logrus.Logger, routesCallback func(*upstream), watchKeyHandler builds.WatchKeyHandler) http.Handler {
2021-02-22 17:27:13 +05:30
up := upstream{
Config: cfg,
accessLogger: accessLogger,
2021-10-27 15:23:28 +05:30
// Kind of a feature flag. See https://gitlab.com/groups/gitlab-org/-/epics/5914#note_564974130
2022-01-26 12:08:38 +05:30
enableGeoProxyFeature: os.Getenv("GEO_SECONDARY_PROXY") != "0",
2021-10-27 15:23:28 +05:30
geoProxyBackend: &url.URL{},
2022-10-11 01:57:18 +05:30
watchKeyHandler: watchKeyHandler,
2021-02-22 17:27:13 +05:30
}
2021-11-18 22:05:49 +05:30
if up.geoProxyPollSleep == nil {
up.geoProxyPollSleep = time.Sleep
}
2021-02-22 17:27:13 +05:30
if up.Backend == nil {
up.Backend = DefaultBackend
}
if up.CableBackend == nil {
up.CableBackend = up.Backend
}
if up.CableSocket == "" {
up.CableSocket = up.Socket
}
2022-08-13 15:12:31 +05:30
up.geoPollerDone = make(chan struct{})
2021-02-22 17:27:13 +05:30
up.RoundTripper = roundtripper.NewBackendRoundTripper(up.Backend, up.Socket, up.ProxyHeadersTimeout, cfg.DevelopmentMode)
up.CableRoundTripper = roundtripper.NewBackendRoundTripper(up.CableBackend, up.CableSocket, up.ProxyHeadersTimeout, cfg.DevelopmentMode)
up.configureURLPrefix()
2021-09-30 23:02:18 +05:30
up.APIClient = apipkg.NewAPI(
up.Backend,
up.Version,
up.RoundTripper,
)
2021-10-27 15:23:28 +05:30
2021-03-05 16:19:46 +05:30
routesCallback(&up)
2021-02-22 17:27:13 +05:30
2022-08-13 15:12:31 +05:30
go up.pollGeoProxyAPI()
2021-10-27 15:23:28 +05:30
2021-02-22 17:27:13 +05:30
var correlationOpts []correlation.InboundHandlerOption
if cfg.PropagateCorrelationID {
correlationOpts = append(correlationOpts, correlation.WithPropagation())
}
2021-10-27 15:23:28 +05:30
if cfg.TrustedCIDRsForPropagation != nil {
correlationOpts = append(correlationOpts, correlation.WithCIDRsTrustedForPropagation(cfg.TrustedCIDRsForPropagation))
}
if cfg.TrustedCIDRsForXForwardedFor != nil {
correlationOpts = append(correlationOpts, correlation.WithCIDRsTrustedForXForwardedFor(cfg.TrustedCIDRsForXForwardedFor))
}
2021-02-22 17:27:13 +05:30
2021-04-17 20:07:23 +05:30
handler := correlation.InjectCorrelationID(&up, correlationOpts...)
2021-10-27 15:23:28 +05:30
// TODO: move to LabKit https://gitlab.com/gitlab-org/gitlab/-/issues/324823
2021-02-22 17:27:13 +05:30
handler = rejectmethods.NewMiddleware(handler)
return handler
}
func (u *upstream) configureURLPrefix() {
relativeURLRoot := u.Backend.Path
if !strings.HasSuffix(relativeURLRoot, "/") {
relativeURLRoot += "/"
}
u.URLPrefix = urlprefix.Prefix(relativeURLRoot)
}
func (u *upstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
helper.FixRemoteAddr(r)
helper.DisableResponseBuffering(w)
// Drop RequestURI == "*" (FIXME: why?)
if r.RequestURI == "*" {
helper.HTTPError(w, r, "Connection upgrade not allowed", http.StatusBadRequest)
return
}
// Disallow connect
if r.Method == "CONNECT" {
helper.HTTPError(w, r, "CONNECT not allowed", http.StatusBadRequest)
return
}
// Check URL Root
2021-03-05 16:19:46 +05:30
URIPath := urlprefix.CleanURIPath(r.URL.EscapedPath())
2021-02-22 17:27:13 +05:30
prefix := u.URLPrefix
if !prefix.Match(URIPath) {
helper.HTTPError(w, r, fmt.Sprintf("Not found %q", URIPath), http.StatusNotFound)
return
}
2021-10-27 15:23:28 +05:30
cleanedPath := prefix.Strip(URIPath)
2021-09-30 23:02:18 +05:30
2021-10-27 15:23:28 +05:30
route := u.findRoute(cleanedPath, r)
2021-02-22 17:27:13 +05:30
if route == nil {
// The protocol spec in git/Documentation/technical/http-protocol.txt
// says we must return 403 if no matching service is found.
helper.HTTPError(w, r, "Forbidden", http.StatusForbidden)
return
}
for _, h := range requestHeaderBlacklist {
r.Header.Del(h)
}
route.handler.ServeHTTP(w, r)
}
2021-10-27 15:23:28 +05:30
func (u *upstream) findRoute(cleanedPath string, r *http.Request) *routeEntry {
2022-08-13 15:12:31 +05:30
if route := u.findGeoProxyRoute(cleanedPath, r); route != nil {
return route
2021-10-27 15:23:28 +05:30
}
for _, ro := range u.Routes {
if ro.isMatch(cleanedPath, r) {
return &ro
}
}
return nil
}
func (u *upstream) findGeoProxyRoute(cleanedPath string, r *http.Request) *routeEntry {
u.mu.RLock()
defer u.mu.RUnlock()
if u.geoProxyBackend.String() == "" {
return nil
}
// Some routes are safe to serve from this GitLab instance
for _, ro := range u.geoLocalRoutes {
if ro.isMatch(cleanedPath, r) {
return &ro
}
}
if cleanedPath == "/-/cable" {
return &u.geoProxyCableRoute
}
return &u.geoProxyRoute
}
func (u *upstream) pollGeoProxyAPI() {
2022-08-13 15:12:31 +05:30
defer close(u.geoPollerDone)
2021-10-27 15:23:28 +05:30
for {
2022-08-13 15:12:31 +05:30
// Check enableGeoProxyFeature every time because `callGeoProxyApi()` can change its value.
// This is can also be disabled through the GEO_SECONDARY_PROXY env var.
if !u.enableGeoProxyFeature {
break
}
2021-10-27 15:23:28 +05:30
u.callGeoProxyAPI()
2021-11-18 22:05:49 +05:30
u.geoProxyPollSleep(geoProxyApiPollingInterval)
2021-10-27 15:23:28 +05:30
}
}
// Calls /api/v4/geo/proxy and sets up routes
func (u *upstream) callGeoProxyAPI() {
2022-06-21 17:19:12 +05:30
geoProxyData, err := u.APIClient.GetGeoProxyData()
2021-10-27 15:23:28 +05:30
if err != nil {
2022-08-13 15:12:31 +05:30
// Unable to determine Geo Proxy URL. Fallback on cached value.
return
}
if !geoProxyData.GeoEnabled {
// When Geo is not enabled, we don't need to proxy, as it unnecessarily polls the
// API, whereas a restart is necessary to enable Geo in the first place; at which
// point we get fresh data from the API.
u.enableGeoProxyFeature = false
2021-10-27 15:23:28 +05:30
return
}
2022-06-21 17:19:12 +05:30
hasProxyDataChanged := false
if u.geoProxyBackend.String() != geoProxyData.GeoProxyURL.String() {
2022-08-13 15:12:31 +05:30
// URL changed
2022-06-21 17:19:12 +05:30
hasProxyDataChanged = true
}
if u.geoProxyExtraData != geoProxyData.GeoProxyExtraData {
2022-08-13 15:12:31 +05:30
// Signed data changed
2022-06-21 17:19:12 +05:30
hasProxyDataChanged = true
}
if hasProxyDataChanged {
u.updateGeoProxyFieldsFromData(geoProxyData)
2021-10-27 15:23:28 +05:30
}
}
2022-06-21 17:19:12 +05:30
func (u *upstream) updateGeoProxyFieldsFromData(geoProxyData *apipkg.GeoProxyData) {
2021-10-27 15:23:28 +05:30
u.mu.Lock()
defer u.mu.Unlock()
2022-06-21 17:19:12 +05:30
u.geoProxyBackend = geoProxyData.GeoProxyURL
u.geoProxyExtraData = geoProxyData.GeoProxyExtraData
2021-11-18 22:05:49 +05:30
if u.geoProxyBackend.String() == "" {
return
}
2022-06-21 17:19:12 +05:30
geoProxyWorkhorseHeaders := map[string]string{
"Gitlab-Workhorse-Geo-Proxy": "1",
"Gitlab-Workhorse-Geo-Proxy-Extra-Data": u.geoProxyExtraData,
}
2021-10-27 15:23:28 +05:30
geoProxyRoundTripper := roundtripper.NewBackendRoundTripper(u.geoProxyBackend, "", u.ProxyHeadersTimeout, u.DevelopmentMode)
2022-03-02 08:16:31 +05:30
geoProxyUpstream := proxypkg.NewProxy(
u.geoProxyBackend,
u.Version,
geoProxyRoundTripper,
proxypkg.WithCustomHeaders(geoProxyWorkhorseHeaders),
2022-06-21 17:19:12 +05:30
proxypkg.WithForcedTargetHostHeader(),
2022-03-02 08:16:31 +05:30
)
2021-10-27 15:23:28 +05:30
u.geoProxyCableRoute = u.wsRoute(`^/-/cable\z`, geoProxyUpstream)
2022-01-26 12:08:38 +05:30
u.geoProxyRoute = u.route("", "", geoProxyUpstream, withGeoProxy())
2021-10-27 15:23:28 +05:30
}