From 0528d10cd6db4c3481ce17ac79d1835ff1b5ee4c Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Fri, 8 Apr 2022 18:05:21 -0600 Subject: [PATCH 01/18] initial changes --- .../traffic_ops_golang/config/config.go | 29 +++++ .../traffic_ops_golang/routing/routing.go | 101 +++++++++++++++++- .../traffic_ops_golang/traffic_ops_golang.go | 40 ++++++- 3 files changed, 165 insertions(+), 5 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index f9680c10a3..55c808c160 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -36,6 +36,21 @@ import ( "github.com/apache/trafficcontrol/lib/go-util" ) +type Options struct { + Algorithm string `json:"alg"` +} + +type SoaRoute struct { + Path string `json:"path"` + Method string `json:"method"` + Hosts []string `json:"hosts"` + Opts Options `json:"opts"` +} + +type SoaConfig struct { + Routes []SoaRoute `json:"routes"` +} + // Config reflects the structure of the cdn.conf file type Config struct { URL *url.URL `json:"-"` @@ -286,6 +301,20 @@ func (c Config) EventLog() log.LogLocation { const BlockStartup = true const AllowStartup = false +func LoadSoaConfig(soaConfigPath string) (SoaConfig, error) { + confBytes, err := ioutil.ReadFile(soaConfigPath) + if err != nil { + return SoaConfig{}, fmt.Errorf("reading CDN conf '%s': %v", soaConfigPath, err) + } + + cfg := SoaConfig{} + err = json.Unmarshal(confBytes, &cfg) + if err != nil { + return SoaConfig{}, fmt.Errorf("unmarshalling '%s': %v", soaConfigPath, err) + } + return cfg, nil +} + func LoadCdnConfig(cdnConfPath string) (Config, error) { // load json from cdn.conf confBytes, err := ioutil.ReadFile(cdnConfPath) diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index ca19d830cf..3c3e6b2f25 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -23,9 +23,12 @@ package routing import ( "context" + "crypto/tls" "errors" "fmt" "net/http" + "net/http/httputil" + "net/url" "regexp" "sort" "strconv" @@ -35,6 +38,7 @@ import ( "github.com/apache/trafficcontrol/lib/go-log" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/plugin" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/routing/middleware" @@ -85,6 +89,8 @@ type ServerData struct { Profiling *bool // Yes this is a field in the config but we want to live reload this value and NOT the entire config Plugins plugin.Plugins TrafficVault trafficvault.TrafficVault + SOAConfig config.SoaConfig + Mux *http.ServeMux } // CompiledRoute ... @@ -226,6 +232,7 @@ func Handler( getReqID func() uint64, plugins plugin.Plugins, tv trafficvault.TrafficVault, + soaConfig config.SoaConfig, w http.ResponseWriter, r *http.Request, ) { @@ -280,8 +287,70 @@ func Handler( h.ServeHTTP(w, r) return } + var soaHandled bool + for _, soaRoute := range soaConfig.Routes { + for _, host := range soaRoute.Hosts { + if soaRoute.Path == r.URL.Path { + soaHandled = true + rp := httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "https", + Opaque: "", + User: nil, + Host: host, + Path: "", + RawPath: "", + ForceQuery: false, + RawQuery: "", + Fragment: "", + RawFragment: "", + }) + rp.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + routeCtx := context.WithValue(ctx, api.DBContextKey, db) + routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) + r = r.WithContext(routeCtx) + r.Header.Add(middleware.RouteID, strconv.Itoa(123456)) + userErr, sysErr, code := HandleSoaRoute(cfg.Secrets[0], w, r) + if userErr != nil || sysErr != nil { + w.WriteHeader(code) + api.HandleErr(w, r, nil, code, userErr, sysErr) + return + } + rp.ServeHTTP(w, r) + return + } + } + } + if !soaHandled { + catchall.ServeHTTP(w, r) + } +} - catchall.ServeHTTP(w, r) +func HandleSoaRoute(secret string, w http.ResponseWriter, r *http.Request) (error, error, int) { + var userErr, sysErr error + var errCode int + var user auth.CurrentUser + var inf *api.APIInfo + + user, userErr, sysErr, errCode = api.GetUserFromReq(w, r, secret) + if userErr != nil || sysErr != nil { + return userErr, sysErr, errCode + } + if user.PrivLevel < auth.PrivLevelReadOnly { + return errors.New("forbidden"), nil, http.StatusForbidden + } + api.AddUserToReq(r, user) + + var params []string + + inf, userErr, sysErr, errCode = api.NewInfo(r, params, nil) + if userErr != nil || sysErr != nil { + return userErr, sysErr, errCode + } + _ = inf.Tx.Tx + defer inf.Close() + return nil, nil, http.StatusOK } // IsRequestAPIAndUnknownVersion returns true if the request starts with `/api` and is a version not in the list of versions. @@ -335,12 +404,38 @@ func RegisterRoutes(d ServerData) error { compiledRoutes := CompileRoutes(routes) getReqID := nextReqIDGetter() - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - Handler(compiledRoutes, versions, catchall, d.DB, &d.Config, getReqID, d.Plugins, d.TrafficVault, w, r) + d.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + Handler(compiledRoutes, versions, catchall, d.DB, &d.Config, getReqID, d.Plugins, d.TrafficVault, d.SOAConfig, w, r) }) return nil } +func ServeSoaRoutes(d ServerData) { + for _, soaRoute := range d.SOAConfig.Routes { + for _, host := range soaRoute.Hosts { + d.Mux = http.NewServeMux() + d.Mux.HandleFunc(soaRoute.Path, func(w http.ResponseWriter, r *http.Request) { + rp := httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "https", + Opaque: "", + User: nil, + Host: host, + Path: "", + RawPath: "", + ForceQuery: false, + RawQuery: "", + Fragment: "", + RawFragment: "", + }) + rp.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + rp.ServeHTTP(w, r) + }) + } + } +} + // nextReqIDGetter returns a function for getting incrementing identifiers. The returned func is safe for calling with multiple goroutines. Note the returned identifiers will not be unique after the max uint64 value. func nextReqIDGetter() func() uint64 { id := uint64(0) diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go index a04c486d36..192f175940 100644 --- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go +++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go @@ -22,6 +22,7 @@ package main import ( "crypto/tls" "encoding/json" + "errors" "flag" "fmt" "io/ioutil" @@ -64,6 +65,7 @@ func main() { configFileName := flag.String("cfg", "", "The config file path") dbConfigFileName := flag.String("dbcfg", "", "The db config file path") riakConfigFileName := flag.String("riakcfg", "", "The riak config file path (DEPRECATED: use traffic_vault_backend = riak and traffic_vault_config in cdn.conf instead)") + soaConfigFileName := flag.String("soacfg", "", "The soa config file path") flag.Parse() if *showVersion { @@ -164,7 +166,17 @@ func main() { log.Errorln(debugServer.ListenAndServe()) }() - if err := routing.RegisterRoutes(routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault}); err != nil { + var soaConfig config.SoaConfig + if *soaConfigFileName != "" { + soaConfig, err = config.LoadSoaConfig(*soaConfigFileName) + if err != nil { + log.Errorf("error loading soa config: %v", err) + } + } + + mux := http.NewServeMux() + d := routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault, SOAConfig: soaConfig, Mux: mux} + if err := routing.RegisterRoutes(d); err != nil { log.Errorf("registering routes: %v\n", err) os.Exit(1) } @@ -214,7 +226,7 @@ func main() { file.Close() } - if err := server.ListenAndServeTLS(cfg.CertPath, cfg.KeyPath); err != nil { + if err := http.ListenAndServeTLS("localhost:"+cfg.Port, cfg.CertPath, cfg.KeyPath, mux); err != nil { log.Errorf("stopping server: %v\n", err) os.Exit(1) } @@ -234,6 +246,12 @@ func main() { reloadProfilingConfig := func() { setNewProfilingInfo(*configFileName, &profiling, &profilingLocation, cfg.Version) + soaConfig, err = setNewSoaConfig(soaConfigFileName) + if err != nil { + log.Errorf("could not reload soa config: %v", err) + } + d.SOAConfig = soaConfig + routing.ServeSoaRoutes(d) } signalReloader(unix.SIGHUP, reloadProfilingConfig) } @@ -293,6 +311,18 @@ func setupTrafficVault(riakConfigFileName string, cfg *config.Config) trafficvau return &disabled.Disabled{} } +func setNewSoaConfig(soaConfigFileName *string) (config.SoaConfig, error) { + if soaConfigFileName == nil { + return config.SoaConfig{}, errors.New("no SOA config filename") + } + soaConfig, err := reloadSoaConfig(*soaConfigFileName) + if err != nil { + log.Errorf("error reloading config: %v", err) + return soaConfig, err + } + return soaConfig, nil +} + func setNewProfilingInfo(configFileName string, currentProfilingEnabled *bool, currentProfilingLocation *string, version string) { newProfilingEnabled, newProfilingLocation, err := reloadProfilingInfo(configFileName) if err != nil { @@ -350,6 +380,12 @@ func reloadProfilingInfo(configFileName string) (bool, string, error) { return cfg.ProfilingEnabled, profilingLocation, nil } +func reloadSoaConfig(soaConfigFileName string) (config.SoaConfig, error) { + log.Infoln("reload SOA config") + soaConfig, err := config.LoadSoaConfig(soaConfigFileName) + return soaConfig, err +} + func continuousProfile(profiling *bool, profilingDir *string, version string) { if *profiling && *profilingDir != "" { go func() { From 6c6c3e04d8589945b0234ff4ebd0c4e2a0d92580 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 11 Apr 2022 23:47:47 -0600 Subject: [PATCH 02/18] working changes --- .../traffic_ops_golang/routing/routing.go | 47 ++++++++----------- .../traffic_ops_golang/traffic_ops_golang.go | 2 +- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 3c3e6b2f25..26b7ffdfb9 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -33,6 +33,7 @@ import ( "sort" "strconv" "strings" + "sync" "sync/atomic" "time" @@ -50,6 +51,21 @@ import ( // RoutePrefix is a prefix that all API routes must match. const RoutePrefix = "^api" // TODO config? +var SOAConfig config.SoaConfig +var Mutex = sync.RWMutex{} + +func GetSOAConfig() config.SoaConfig { + Mutex.RLock() + defer Mutex.RUnlock() + return SOAConfig +} + +func SetSOAConfig(soaConfig config.SoaConfig) { + Mutex.Lock() + defer Mutex.Unlock() + SOAConfig = soaConfig +} + // A Route defines an association with a client request and a handler for that // request. type Route struct { @@ -232,7 +248,6 @@ func Handler( getReqID func() uint64, plugins plugin.Plugins, tv trafficvault.TrafficVault, - soaConfig config.SoaConfig, w http.ResponseWriter, r *http.Request, ) { @@ -288,6 +303,7 @@ func Handler( return } var soaHandled bool + soaConfig := GetSOAConfig() for _, soaRoute := range soaConfig.Routes { for _, host := range soaRoute.Hosts { if soaRoute.Path == r.URL.Path { @@ -404,38 +420,13 @@ func RegisterRoutes(d ServerData) error { compiledRoutes := CompileRoutes(routes) getReqID := nextReqIDGetter() + SetSOAConfig(d.SOAConfig) d.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - Handler(compiledRoutes, versions, catchall, d.DB, &d.Config, getReqID, d.Plugins, d.TrafficVault, d.SOAConfig, w, r) + Handler(compiledRoutes, versions, catchall, d.DB, &d.Config, getReqID, d.Plugins, d.TrafficVault, w, r) }) return nil } -func ServeSoaRoutes(d ServerData) { - for _, soaRoute := range d.SOAConfig.Routes { - for _, host := range soaRoute.Hosts { - d.Mux = http.NewServeMux() - d.Mux.HandleFunc(soaRoute.Path, func(w http.ResponseWriter, r *http.Request) { - rp := httputil.NewSingleHostReverseProxy(&url.URL{ - Scheme: "https", - Opaque: "", - User: nil, - Host: host, - Path: "", - RawPath: "", - ForceQuery: false, - RawQuery: "", - Fragment: "", - RawFragment: "", - }) - rp.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - rp.ServeHTTP(w, r) - }) - } - } -} - // nextReqIDGetter returns a function for getting incrementing identifiers. The returned func is safe for calling with multiple goroutines. Note the returned identifiers will not be unique after the max uint64 value. func nextReqIDGetter() func() uint64 { id := uint64(0) diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go index 192f175940..26ce1772aa 100644 --- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go +++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go @@ -251,7 +251,7 @@ func main() { log.Errorf("could not reload soa config: %v", err) } d.SOAConfig = soaConfig - routing.ServeSoaRoutes(d) + routing.SetSOAConfig(soaConfig) } signalReloader(unix.SIGHUP, reloadProfilingConfig) } From 2ca9b3d76acfdbe5f7791b3834bb756ad2e4b395 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 12:28:23 -0600 Subject: [PATCH 03/18] formatting and cleanup --- .../traffic_ops_golang/config/config.go | 1 + .../traffic_ops_golang/routing/routing.go | 54 +++++++++---------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index 55c808c160..80953ffa25 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -45,6 +45,7 @@ type SoaRoute struct { Method string `json:"method"` Hosts []string `json:"hosts"` Opts Options `json:"opts"` + Index int } type SoaConfig struct { diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 26b7ffdfb9..cdcf4fb3a9 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -304,38 +304,32 @@ func Handler( } var soaHandled bool soaConfig := GetSOAConfig() - for _, soaRoute := range soaConfig.Routes { - for _, host := range soaRoute.Hosts { - if soaRoute.Path == r.URL.Path { - soaHandled = true - rp := httputil.NewSingleHostReverseProxy(&url.URL{ - Scheme: "https", - Opaque: "", - User: nil, - Host: host, - Path: "", - RawPath: "", - ForceQuery: false, - RawQuery: "", - Fragment: "", - RawFragment: "", - }) - rp.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - routeCtx := context.WithValue(ctx, api.DBContextKey, db) - routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) - r = r.WithContext(routeCtx) - r.Header.Add(middleware.RouteID, strconv.Itoa(123456)) - userErr, sysErr, code := HandleSoaRoute(cfg.Secrets[0], w, r) - if userErr != nil || sysErr != nil { - w.WriteHeader(code) - api.HandleErr(w, r, nil, code, userErr, sysErr) - return - } - rp.ServeHTTP(w, r) + for i, soaRoute := range soaConfig.Routes { + if soaRoute.Path == r.URL.Path && soaRoute.Method == r.Method { + index := soaRoute.Index % len(soaRoute.Hosts) + host := soaRoute.Hosts[index] + soaRoute.Index++ + soaConfig.Routes[i] = soaRoute + soaHandled = true + rp := httputil.NewSingleHostReverseProxy(&url.URL{ + Host: host, + Scheme: cfg.URL.Scheme, + }) + rp.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + routeCtx := context.WithValue(ctx, api.DBContextKey, db) + routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) + r = r.WithContext(routeCtx) + r.Header.Add(middleware.RouteID, strconv.Itoa(123456)) + userErr, sysErr, code := HandleSoaRoute(cfg.Secrets[0], w, r) + if userErr != nil || sysErr != nil { + w.WriteHeader(code) + api.HandleErr(w, r, nil, code, userErr, sysErr) return } + rp.ServeHTTP(w, r) + return } } if !soaHandled { From 04b8c730bf4abb4f2f72ddedcbd1d1cd37d1fd07 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 12:58:31 -0600 Subject: [PATCH 04/18] cleanup --- traffic_ops/traffic_ops_golang/config/config.go | 1 + traffic_ops/traffic_ops_golang/routing/routing.go | 2 +- traffic_ops/traffic_ops_golang/traffic_ops_golang.go | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index 80953ffa25..c7437b66b6 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -45,6 +45,7 @@ type SoaRoute struct { Method string `json:"method"` Hosts []string `json:"hosts"` Opts Options `json:"opts"` + ID int `json:"route_id"` Index int } diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index cdcf4fb3a9..f6192d19fe 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -321,7 +321,7 @@ func Handler( routeCtx := context.WithValue(ctx, api.DBContextKey, db) routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) r = r.WithContext(routeCtx) - r.Header.Add(middleware.RouteID, strconv.Itoa(123456)) + r.Header.Add(middleware.RouteID, strconv.Itoa(soaRoute.ID)) userErr, sysErr, code := HandleSoaRoute(cfg.Secrets[0], w, r) if userErr != nil || sysErr != nil { w.WriteHeader(code) diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go index 26ce1772aa..bc159616bc 100644 --- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go +++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go @@ -226,7 +226,7 @@ func main() { file.Close() } - if err := http.ListenAndServeTLS("localhost:"+cfg.Port, cfg.CertPath, cfg.KeyPath, mux); err != nil { + if err := http.ListenAndServeTLS(server.Addr, cfg.CertPath, cfg.KeyPath, mux); err != nil { log.Errorf("stopping server: %v\n", err) os.Exit(1) } @@ -249,9 +249,10 @@ func main() { soaConfig, err = setNewSoaConfig(soaConfigFileName) if err != nil { log.Errorf("could not reload soa config: %v", err) + } else { + d.SOAConfig = soaConfig + routing.SetSOAConfig(soaConfig) } - d.SOAConfig = soaConfig - routing.SetSOAConfig(soaConfig) } signalReloader(unix.SIGHUP, reloadProfilingConfig) } From 73aa56ff1e8dfe7d108c89e7b039707022373b80 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 15:12:43 -0600 Subject: [PATCH 05/18] adding log handlers --- traffic_ops/traffic_ops_golang/routing/routing.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index f6192d19fe..464398e124 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -318,6 +318,10 @@ func Handler( rp.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } + rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { + api.HandleErr(w, r, nil, http.StatusInternalServerError, nil, err) + return + } routeCtx := context.WithValue(ctx, api.DBContextKey, db) routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) r = r.WithContext(routeCtx) @@ -328,7 +332,8 @@ func Handler( api.HandleErr(w, r, nil, code, userErr, sysErr) return } - rp.ServeHTTP(w, r) + backendHandler := middleware.WrapAccessLog(cfg.Secrets[0], rp) + backendHandler.ServeHTTP(w, r) return } } @@ -358,7 +363,6 @@ func HandleSoaRoute(secret string, w http.ResponseWriter, r *http.Request) (erro if userErr != nil || sysErr != nil { return userErr, sysErr, errCode } - _ = inf.Tx.Tx defer inf.Close() return nil, nil, http.StatusOK } From 93e31ef600df75d64442e6eb60af8772f0f0a6df Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 16:10:55 -0600 Subject: [PATCH 06/18] add dcos, cleanup --- CHANGELOG.md | 1 + docs/source/admin/traffic_ops.rst | 27 ++++- traffic_ops/app/conf/backends.conf | 26 +++++ traffic_ops/app/conf/production/backends.conf | 26 +++++ traffic_ops/etc/init.d/traffic_ops | 2 +- .../traffic_ops_golang/config/config.go | 16 +-- .../traffic_ops_golang/routing/routing.go | 99 ++++++++++--------- .../traffic_ops_golang/traffic_ops_golang.go | 40 ++++---- 8 files changed, 163 insertions(+), 74 deletions(-) create mode 100644 traffic_ops/app/conf/backends.conf create mode 100644 traffic_ops/app/conf/production/backends.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b8ed45911..43c0f56f05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Replaces all Traffic Portal Tenant select boxes with a novel tree select box [#6427](https://github.com/apache/trafficcontrol/issues/6427). - Traffic Monitor: Add support for `access.log` to TM. - Added functionality for login to provide a Bearer token and for that token to be later used for authorization. +- [Traffic Ops] Added support for backend configurations so that Traffic Ops can be run as a Service Oriented architecture(SOA) product [#6754](https://github.com/apache/trafficcontrol/pull/6754). - [Traffic Ops | Traffic Go Clients | T3C] Add additional timestamp fields to server for queuing and dequeueing config and revalidate updates. ### Fixed diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index 601391b501..6ff565481f 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -257,7 +257,7 @@ While this section contains instructions for running Traffic Ops manually, the o traffic_ops_golang ------------------ -``traffic_ops_golang [--version] [--plugins] [--api-routes] --cfg CONFIG_PATH --dbcfg DB_CONFIG_PATH [--riakcfg RIAK_CONFIG_PATH]`` +``traffic_ops_golang [--version] [--plugins] [--api-routes] --cfg CONFIG_PATH --dbcfg DB_CONFIG_PATH [--riakcfg RIAK_CONFIG_PATH] [--backendcfg BACKEND_CONFIG_PATH]`` .. option:: --cfg CONFIG_PATH @@ -282,6 +282,10 @@ traffic_ops_golang .. impl-detail:: The name of this flag is derived from the current database used in the implementation of Traffic Vault - `Riak KV `_. +.. option:: --backendcfg BACKEND_CONFIG_PATH + + This optional command line flag specifies the absolute or relative path to a configuration file used by Traffic Ops to act as a reverse proxy and forward requests on the specified paths to the corresponding hosts - `backends.conf`_ + .. option:: --version Print version information and exit. @@ -586,6 +590,27 @@ This file sets authentication options for connections to Riak when used as the T .. impl-detail:: The name of this file is derived from the current database used in the implementation of Traffic Vault - `Riak KV `_. +.. _backends.conf: + +backends.conf +""""""""""""" +This file deals with the configuration parameters of running Traffic Ops as a reverse proxy for certain endpoints that need to be served externally by other backend services. It is a JSON-format set of options and their respective values. `traffic_ops_golang`_ will use whatever file is specified (if any) by its :option:`--backendcfg` option. The keys of the file are described below. + +:routes: This is an array of options to configure Traffic Ops to forward requests of specified types to the appropriate backends. + + :path: The endpoint that will be served by the backend, for example, `api/4.0/foo`. + :method: The HTTP method for the above mentioned path, for example, `GET` or `PUT`. + :route_id: The integral identifier for the new route being added. + :hosts: An array of the hosts and ports where the request (if matched) needs to be forwarded to, for example, `cdn-foo-backend-service-host:9090`. + :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, `"alg": "roundrobin"`. Currently, only `roundrobin` is supported (which is also the default if nothing is specified) by Traffic Ops. + +Example backends.conf +''''''''''''''''''''' +.. include:: ../../../traffic_ops/app/conf/backends.conf + :code: json + :tab-width: 4 + + Installing the SSL Certificate ------------------------------ By default, Traffic Ops runs as an SSL web server (that is, over HTTPS), and a certificate needs to be installed. diff --git a/traffic_ops/app/conf/backends.conf b/traffic_ops/app/conf/backends.conf new file mode 100644 index 0000000000..c1200d6d9e --- /dev/null +++ b/traffic_ops/app/conf/backends.conf @@ -0,0 +1,26 @@ +{ + "routes": [ + { + "path": "/api/4.0/foo", + "method": "GET", + "hosts": [ + "localhost:8444", + "localhost:8445" + ], + "opts": { + "alg": "roundrobin" + } + }, + { + "path": "/api/4.0/bar", + "method": "GET", + "hosts": [ + "localhost:8444", + "localhost:9090" + ], + "opts": { + "alg": "roundrobin" + } + } + ] +} diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf new file mode 100644 index 0000000000..c1200d6d9e --- /dev/null +++ b/traffic_ops/app/conf/production/backends.conf @@ -0,0 +1,26 @@ +{ + "routes": [ + { + "path": "/api/4.0/foo", + "method": "GET", + "hosts": [ + "localhost:8444", + "localhost:8445" + ], + "opts": { + "alg": "roundrobin" + } + }, + { + "path": "/api/4.0/bar", + "method": "GET", + "hosts": [ + "localhost:8444", + "localhost:9090" + ], + "opts": { + "alg": "roundrobin" + } + } + ] +} diff --git a/traffic_ops/etc/init.d/traffic_ops b/traffic_ops/etc/init.d/traffic_ops index d2fad881b1..12674c981b 100755 --- a/traffic_ops/etc/init.d/traffic_ops +++ b/traffic_ops/etc/init.d/traffic_ops @@ -50,7 +50,7 @@ start () stop echo -e "Starting Traffic Ops\n" ulimit -n 200000 || echo "Setting ulimit max files failed for traffic_ops_golang" - cd $TO_DIR && $TO_DIR/bin/traffic_ops_golang -cfg $TO_DIR/conf/cdn.conf -dbcfg $TO_DIR/conf/production/database.conf -riakcfg $TO_DIR/conf/production/riak.conf & + cd $TO_DIR && $TO_DIR/bin/traffic_ops_golang -cfg $TO_DIR/conf/cdn.conf -dbcfg $TO_DIR/conf/production/database.conf -riakcfg $TO_DIR/conf/production/riak.conf -backendcfg $TO_DIR/conf/production/backends.conf & } stop () diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index c7437b66b6..f712becac4 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -40,7 +40,7 @@ type Options struct { Algorithm string `json:"alg"` } -type SoaRoute struct { +type BackendRoute struct { Path string `json:"path"` Method string `json:"method"` Hosts []string `json:"hosts"` @@ -49,8 +49,8 @@ type SoaRoute struct { Index int } -type SoaConfig struct { - Routes []SoaRoute `json:"routes"` +type BackendConfig struct { + Routes []BackendRoute `json:"routes"` } // Config reflects the structure of the cdn.conf file @@ -303,16 +303,16 @@ func (c Config) EventLog() log.LogLocation { const BlockStartup = true const AllowStartup = false -func LoadSoaConfig(soaConfigPath string) (SoaConfig, error) { - confBytes, err := ioutil.ReadFile(soaConfigPath) +func LoadBackendConfig(backendConfigPath string) (BackendConfig, error) { + confBytes, err := ioutil.ReadFile(backendConfigPath) if err != nil { - return SoaConfig{}, fmt.Errorf("reading CDN conf '%s': %v", soaConfigPath, err) + return BackendConfig{}, fmt.Errorf("reading backend conf '%s': %v", backendConfigPath, err) } - cfg := SoaConfig{} + cfg := BackendConfig{} err = json.Unmarshal(confBytes, &cfg) if err != nil { - return SoaConfig{}, fmt.Errorf("unmarshalling '%s': %v", soaConfigPath, err) + return BackendConfig{}, fmt.Errorf("unmarshalling '%s': %v", backendConfigPath, err) } return cfg, nil } diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 464398e124..689f4eb22d 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -51,19 +51,24 @@ import ( // RoutePrefix is a prefix that all API routes must match. const RoutePrefix = "^api" // TODO config? -var SOAConfig config.SoaConfig +// BackendConfig stores the current backend config supplied to traffic ops. +var BackendConfig config.BackendConfig + +// Mutex is a mutex for safely reading/ writing to BackendConfig. var Mutex = sync.RWMutex{} -func GetSOAConfig() config.SoaConfig { +// GetBackendConfig returns the current BackendConfig. +func GetBackendConfig() config.BackendConfig { Mutex.RLock() defer Mutex.RUnlock() - return SOAConfig + return BackendConfig } -func SetSOAConfig(soaConfig config.SoaConfig) { +// SetBackendConfig sets the BackendConfig to the value supplied. +func SetBackendConfig(backendConfig config.BackendConfig) { Mutex.Lock() defer Mutex.Unlock() - SOAConfig = soaConfig + BackendConfig = backendConfig } // A Route defines an association with a client request and a handler for that @@ -101,12 +106,12 @@ func (r *Route) SetMiddleware(authBase middleware.AuthBase, requestTimeout time. // ServerData ... type ServerData struct { config.Config - DB *sqlx.DB - Profiling *bool // Yes this is a field in the config but we want to live reload this value and NOT the entire config - Plugins plugin.Plugins - TrafficVault trafficvault.TrafficVault - SOAConfig config.SoaConfig - Mux *http.ServeMux + DB *sqlx.DB + Profiling *bool // Yes this is a field in the config but we want to live reload this value and NOT the entire config + Plugins plugin.Plugins + TrafficVault trafficvault.TrafficVault + BackendConfig config.BackendConfig + Mux *http.ServeMux } // CompiledRoute ... @@ -302,47 +307,53 @@ func Handler( h.ServeHTTP(w, r) return } - var soaHandled bool - soaConfig := GetSOAConfig() - for i, soaRoute := range soaConfig.Routes { - if soaRoute.Path == r.URL.Path && soaRoute.Method == r.Method { - index := soaRoute.Index % len(soaRoute.Hosts) - host := soaRoute.Hosts[index] - soaRoute.Index++ - soaConfig.Routes[i] = soaRoute - soaHandled = true - rp := httputil.NewSingleHostReverseProxy(&url.URL{ - Host: host, - Scheme: cfg.URL.Scheme, - }) - rp.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { - api.HandleErr(w, r, nil, http.StatusInternalServerError, nil, err) + var backendRouteHandled bool + backendConfig := GetBackendConfig() + for i, backendRoute := range backendConfig.Routes { + if backendRoute.Path == r.URL.Path && backendRoute.Method == r.Method { + if backendRoute.Opts.Algorithm == "" || backendRoute.Opts.Algorithm == "roundrobin" { + index := backendRoute.Index % len(backendRoute.Hosts) + host := backendRoute.Hosts[index] + backendRoute.Index++ + backendConfig.Routes[i] = backendRoute + backendRouteHandled = true + rp := httputil.NewSingleHostReverseProxy(&url.URL{ + Host: host, + Scheme: cfg.URL.Scheme, + }) + rp.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { + api.HandleErr(w, r, nil, http.StatusInternalServerError, nil, err) + return + } + routeCtx := context.WithValue(ctx, api.DBContextKey, db) + routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) + r = r.WithContext(routeCtx) + r.Header.Add(middleware.RouteID, strconv.Itoa(backendRoute.ID)) + userErr, sysErr, code := HandleBackendRoute(cfg.Secrets[0], w, r) + if userErr != nil || sysErr != nil { + w.WriteHeader(code) + api.HandleErr(w, r, nil, code, userErr, sysErr) + return + } + backendHandler := middleware.WrapAccessLog(cfg.Secrets[0], rp) + backendHandler.ServeHTTP(w, r) return - } - routeCtx := context.WithValue(ctx, api.DBContextKey, db) - routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) - r = r.WithContext(routeCtx) - r.Header.Add(middleware.RouteID, strconv.Itoa(soaRoute.ID)) - userErr, sysErr, code := HandleSoaRoute(cfg.Secrets[0], w, r) - if userErr != nil || sysErr != nil { - w.WriteHeader(code) - api.HandleErr(w, r, nil, code, userErr, sysErr) + } else { + api.HandleErr(w, r, nil, http.StatusBadRequest, errors.New("only an algorithm of roundrobin is supported by the backend options currently"), nil) return } - backendHandler := middleware.WrapAccessLog(cfg.Secrets[0], rp) - backendHandler.ServeHTTP(w, r) - return } } - if !soaHandled { + if !backendRouteHandled { catchall.ServeHTTP(w, r) } } -func HandleSoaRoute(secret string, w http.ResponseWriter, r *http.Request) (error, error, int) { +// HandleBackendRoute does all the pre processing for the backend routes. +func HandleBackendRoute(secret string, w http.ResponseWriter, r *http.Request) (error, error, int) { var userErr, sysErr error var errCode int var user auth.CurrentUser @@ -418,7 +429,7 @@ func RegisterRoutes(d ServerData) error { compiledRoutes := CompileRoutes(routes) getReqID := nextReqIDGetter() - SetSOAConfig(d.SOAConfig) + SetBackendConfig(d.BackendConfig) d.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { Handler(compiledRoutes, versions, catchall, d.DB, &d.Config, getReqID, d.Plugins, d.TrafficVault, w, r) }) diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go index bc159616bc..b7d49728ea 100644 --- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go +++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go @@ -65,7 +65,7 @@ func main() { configFileName := flag.String("cfg", "", "The config file path") dbConfigFileName := flag.String("dbcfg", "", "The db config file path") riakConfigFileName := flag.String("riakcfg", "", "The riak config file path (DEPRECATED: use traffic_vault_backend = riak and traffic_vault_config in cdn.conf instead)") - soaConfigFileName := flag.String("soacfg", "", "The soa config file path") + backendConfigFileName := flag.String("backendcfg", "", "The backend config file path") flag.Parse() if *showVersion { @@ -166,16 +166,16 @@ func main() { log.Errorln(debugServer.ListenAndServe()) }() - var soaConfig config.SoaConfig - if *soaConfigFileName != "" { - soaConfig, err = config.LoadSoaConfig(*soaConfigFileName) + var backendConfig config.BackendConfig + if *backendConfigFileName != "" { + backendConfig, err = config.LoadBackendConfig(*backendConfigFileName) if err != nil { - log.Errorf("error loading soa config: %v", err) + log.Errorf("error loading backend config: %v", err) } } mux := http.NewServeMux() - d := routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault, SOAConfig: soaConfig, Mux: mux} + d := routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault, BackendConfig: backendConfig, Mux: mux} if err := routing.RegisterRoutes(d); err != nil { log.Errorf("registering routes: %v\n", err) os.Exit(1) @@ -246,12 +246,12 @@ func main() { reloadProfilingConfig := func() { setNewProfilingInfo(*configFileName, &profiling, &profilingLocation, cfg.Version) - soaConfig, err = setNewSoaConfig(soaConfigFileName) + backendConfig, err = setNewBackendConfig(backendConfigFileName) if err != nil { - log.Errorf("could not reload soa config: %v", err) + log.Errorf("could not reload backend config: %v", err) } else { - d.SOAConfig = soaConfig - routing.SetSOAConfig(soaConfig) + d.BackendConfig = backendConfig + routing.SetBackendConfig(backendConfig) } } signalReloader(unix.SIGHUP, reloadProfilingConfig) @@ -312,16 +312,16 @@ func setupTrafficVault(riakConfigFileName string, cfg *config.Config) trafficvau return &disabled.Disabled{} } -func setNewSoaConfig(soaConfigFileName *string) (config.SoaConfig, error) { - if soaConfigFileName == nil { - return config.SoaConfig{}, errors.New("no SOA config filename") +func setNewBackendConfig(backendConfigFileName *string) (config.BackendConfig, error) { + if backendConfigFileName == nil { + return config.BackendConfig{}, errors.New("no backend config filename") } - soaConfig, err := reloadSoaConfig(*soaConfigFileName) + backendConfig, err := reloadBackendConfig(*backendConfigFileName) if err != nil { log.Errorf("error reloading config: %v", err) - return soaConfig, err + return backendConfig, err } - return soaConfig, nil + return backendConfig, nil } func setNewProfilingInfo(configFileName string, currentProfilingEnabled *bool, currentProfilingLocation *string, version string) { @@ -381,10 +381,10 @@ func reloadProfilingInfo(configFileName string) (bool, string, error) { return cfg.ProfilingEnabled, profilingLocation, nil } -func reloadSoaConfig(soaConfigFileName string) (config.SoaConfig, error) { - log.Infoln("reload SOA config") - soaConfig, err := config.LoadSoaConfig(soaConfigFileName) - return soaConfig, err +func reloadBackendConfig(backendConfigFileName string) (config.BackendConfig, error) { + log.Infoln("reload backend config") + backendConfig, err := config.LoadBackendConfig(backendConfigFileName) + return backendConfig, err } func continuousProfile(profiling *bool, profilingDir *string, version string) { From d8726b6ed408cd7a63a16ca6a42002cef4ca1430 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 16:12:23 -0600 Subject: [PATCH 07/18] add todos --- traffic_ops/traffic_ops_golang/routing/routing.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 689f4eb22d..521e4dbd53 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -363,6 +363,8 @@ func HandleBackendRoute(secret string, w http.ResponseWriter, r *http.Request) ( if userErr != nil || sysErr != nil { return userErr, sysErr, errCode } + // Todo: change this to check the actual priv levels + // Todo: add permission checks if user.PrivLevel < auth.PrivLevelReadOnly { return errors.New("forbidden"), nil, http.StatusForbidden } From af799c85503b7004b9614472b37b1250328672a2 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 16:14:10 -0600 Subject: [PATCH 08/18] add todos --- traffic_ops/traffic_ops_golang/routing/routing.go | 1 + 1 file changed, 1 insertion(+) diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 521e4dbd53..6fb3bcd91f 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -321,6 +321,7 @@ func Handler( Host: host, Scheme: cfg.URL.Scheme, }) + // Todo: remove this rp.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } From 019a6100dfb9f3b367deebee4d3e852d6b280779 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 16:37:18 -0600 Subject: [PATCH 09/18] cleanup config --- traffic_ops/traffic_ops_golang/routing/routing.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 6fb3bcd91f..9666f56fb3 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -321,9 +321,8 @@ func Handler( Host: host, Scheme: cfg.URL.Scheme, }) - // Todo: remove this rp.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.Insecure}, } rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { api.HandleErr(w, r, nil, http.StatusInternalServerError, nil, err) From a26a10014edf150b0dd8e1a2977becc0e567d502 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 13 Apr 2022 18:57:59 -0600 Subject: [PATCH 10/18] add insecure option to backend config --- docs/source/admin/traffic_ops.rst | 1 + traffic_ops/app/conf/backends.conf | 2 ++ traffic_ops/app/conf/production/backends.conf | 2 ++ traffic_ops/traffic_ops_golang/config/config.go | 13 +++++++------ traffic_ops/traffic_ops_golang/routing/routing.go | 10 ++++++++-- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index 6ff565481f..9f0d022c31 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -602,6 +602,7 @@ This file deals with the configuration parameters of running Traffic Ops as a re :method: The HTTP method for the above mentioned path, for example, `GET` or `PUT`. :route_id: The integral identifier for the new route being added. :hosts: An array of the hosts and ports where the request (if matched) needs to be forwarded to, for example, `cdn-foo-backend-service-host:9090`. + :insecure: A boolean specifying whether or not to enable `InsecureSkipVerify`. :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, `"alg": "roundrobin"`. Currently, only `roundrobin` is supported (which is also the default if nothing is specified) by Traffic Ops. Example backends.conf diff --git a/traffic_ops/app/conf/backends.conf b/traffic_ops/app/conf/backends.conf index c1200d6d9e..4606d575f7 100644 --- a/traffic_ops/app/conf/backends.conf +++ b/traffic_ops/app/conf/backends.conf @@ -7,6 +7,7 @@ "localhost:8444", "localhost:8445" ], + "insecure": true, "opts": { "alg": "roundrobin" } @@ -18,6 +19,7 @@ "localhost:8444", "localhost:9090" ], + "insecure": true, "opts": { "alg": "roundrobin" } diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf index c1200d6d9e..4606d575f7 100644 --- a/traffic_ops/app/conf/production/backends.conf +++ b/traffic_ops/app/conf/production/backends.conf @@ -7,6 +7,7 @@ "localhost:8444", "localhost:8445" ], + "insecure": true, "opts": { "alg": "roundrobin" } @@ -18,6 +19,7 @@ "localhost:8444", "localhost:9090" ], + "insecure": true, "opts": { "alg": "roundrobin" } diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index f712becac4..98ca30d83c 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -41,12 +41,13 @@ type Options struct { } type BackendRoute struct { - Path string `json:"path"` - Method string `json:"method"` - Hosts []string `json:"hosts"` - Opts Options `json:"opts"` - ID int `json:"route_id"` - Index int + Path string `json:"path"` + Method string `json:"method"` + Hosts []string `json:"hosts"` + Opts Options `json:"opts"` + ID int `json:"route_id"` + Insecure bool `json:"insecure"` + Index int } type BackendConfig struct { diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 9666f56fb3..0a7fe253ee 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -321,8 +321,14 @@ func Handler( Host: host, Scheme: cfg.URL.Scheme, }) - rp.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.Insecure}, + if backendRoute.Insecure { + rp.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } else { + rp.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{}, + } } rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { api.HandleErr(w, r, nil, http.StatusInternalServerError, nil, err) From b20595e4892e994f317cb735c659ee4abfd4c91b Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 18 Apr 2022 17:04:24 -0600 Subject: [PATCH 11/18] Adding proper log handlers --- docs/source/admin/traffic_ops.rst | 6 ++- traffic_ops/app/conf/backends.conf | 18 +++++++-- traffic_ops/app/conf/production/backends.conf | 18 +++++++-- .../traffic_ops_golang/config/config.go | 16 ++++---- .../routing/middleware/wrappers.go | 8 ++++ .../traffic_ops_golang/routing/routing.go | 37 ++++++++++++++----- 6 files changed, 78 insertions(+), 25 deletions(-) diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index 9f0d022c31..1f7b08ee12 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -600,9 +600,11 @@ This file deals with the configuration parameters of running Traffic Ops as a re :path: The endpoint that will be served by the backend, for example, `api/4.0/foo`. :method: The HTTP method for the above mentioned path, for example, `GET` or `PUT`. - :route_id: The integral identifier for the new route being added. + :routeId: The integral identifier for the new route being added. :hosts: An array of the hosts and ports where the request (if matched) needs to be forwarded to, for example, `cdn-foo-backend-service-host:9090`. - :insecure: A boolean specifying whether or not to enable `InsecureSkipVerify`. + :insecure: A boolean specifying whether or not to enable `InsecureSkipVerify`. This is an optional parameter, defaulting to `false` when not present. + :privLevel: An integer designating the minimum privilege level required to use this API route. + :permissions: An array of permissions(strings) specifying the permissions required by the user to use this API route. :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, `"alg": "roundrobin"`. Currently, only `roundrobin` is supported (which is also the default if nothing is specified) by Traffic Ops. Example backends.conf diff --git a/traffic_ops/app/conf/backends.conf b/traffic_ops/app/conf/backends.conf index 4606d575f7..715bc3718a 100644 --- a/traffic_ops/app/conf/backends.conf +++ b/traffic_ops/app/conf/backends.conf @@ -8,21 +8,33 @@ "localhost:8445" ], "insecure": true, + "privLevel": 10, + "permissions": [ + "CDN:READ", + "CDN:WRITE" + ], + "routeId": 123456, "opts": { "alg": "roundrobin" } }, { - "path": "/api/4.0/bar", + "path": "/api/4.0/foos", "method": "GET", "hosts": [ "localhost:8444", - "localhost:9090" + "localhost:8445" ], "insecure": true, + "privLevel": 10, + "permissions": [ + "CDN:READ", + "CDN:WRITE" + ], + "routeId": 123457, "opts": { "alg": "roundrobin" } } ] -} +} \ No newline at end of file diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf index 4606d575f7..715bc3718a 100644 --- a/traffic_ops/app/conf/production/backends.conf +++ b/traffic_ops/app/conf/production/backends.conf @@ -8,21 +8,33 @@ "localhost:8445" ], "insecure": true, + "privLevel": 10, + "permissions": [ + "CDN:READ", + "CDN:WRITE" + ], + "routeId": 123456, "opts": { "alg": "roundrobin" } }, { - "path": "/api/4.0/bar", + "path": "/api/4.0/foos", "method": "GET", "hosts": [ "localhost:8444", - "localhost:9090" + "localhost:8445" ], "insecure": true, + "privLevel": 10, + "permissions": [ + "CDN:READ", + "CDN:WRITE" + ], + "routeId": 123457, "opts": { "alg": "roundrobin" } } ] -} +} \ No newline at end of file diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index 98ca30d83c..79d9fb3ffb 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -41,13 +41,15 @@ type Options struct { } type BackendRoute struct { - Path string `json:"path"` - Method string `json:"method"` - Hosts []string `json:"hosts"` - Opts Options `json:"opts"` - ID int `json:"route_id"` - Insecure bool `json:"insecure"` - Index int + Path string `json:"path"` + Method string `json:"method"` + Hosts []string `json:"hosts"` + Opts Options `json:"opts"` + ID int `json:"routeId"` + Insecure bool `json:"insecure"` + Permissions []string `json:"permissions"` + PrivLevel int `json:"privLevel"` + Index int } type BackendConfig struct { diff --git a/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go b/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go index e307faadb9..a4a9aae66b 100644 --- a/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go +++ b/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go @@ -276,6 +276,14 @@ func NotImplementedHandler() http.Handler { }) } +func BackendErrorHandler(code int, userErr error, sysErr error) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(rfc.ContentType, rfc.ApplicationJSON) + w.WriteHeader(code) + api.HandleErr(w, r, nil, code, userErr, sysErr) + }) +} + // DisabledRouteHandler returns a http.Handler which returns a HTTP 5xx code to the client, and an error message indicating the route is currently disabled. // This is used for routes which have been disabled via configuration. See config.ConfigTrafficOpsGolang.RoutingBlacklist.DisabledRoutes. func DisabledRouteHandler() http.Handler { diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 0a7fe253ee..7288a40f21 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -338,17 +338,18 @@ func Handler( routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) r = r.WithContext(routeCtx) r.Header.Add(middleware.RouteID, strconv.Itoa(backendRoute.ID)) - userErr, sysErr, code := HandleBackendRoute(cfg.Secrets[0], w, r) + userErr, sysErr, code := HandleBackendRoute(cfg, backendRoute, w, r) if userErr != nil || sysErr != nil { - w.WriteHeader(code) - api.HandleErr(w, r, nil, code, userErr, sysErr) + h2 := middleware.WrapAccessLog(cfg.Secrets[0], middleware.BackendErrorHandler(code, userErr, sysErr)) + h2.ServeHTTP(w, r) return } backendHandler := middleware.WrapAccessLog(cfg.Secrets[0], rp) backendHandler.ServeHTTP(w, r) return } else { - api.HandleErr(w, r, nil, http.StatusBadRequest, errors.New("only an algorithm of roundrobin is supported by the backend options currently"), nil) + h2 := middleware.WrapAccessLog(cfg.Secrets[0], middleware.BackendErrorHandler(http.StatusBadRequest, errors.New("only an algorithm of roundrobin is supported by the backend options currently"), nil)) + h2.ServeHTTP(w, r) return } } @@ -359,20 +360,36 @@ func Handler( } // HandleBackendRoute does all the pre processing for the backend routes. -func HandleBackendRoute(secret string, w http.ResponseWriter, r *http.Request) (error, error, int) { +func HandleBackendRoute(cfg *config.Config, route config.BackendRoute, w http.ResponseWriter, r *http.Request) (error, error, int) { var userErr, sysErr error var errCode int var user auth.CurrentUser var inf *api.APIInfo - user, userErr, sysErr, errCode = api.GetUserFromReq(w, r, secret) + user, userErr, sysErr, errCode = api.GetUserFromReq(w, r, cfg.Secrets[0]) if userErr != nil || sysErr != nil { return userErr, sysErr, errCode } - // Todo: change this to check the actual priv levels - // Todo: add permission checks - if user.PrivLevel < auth.PrivLevelReadOnly { - return errors.New("forbidden"), nil, http.StatusForbidden + v := api.GetRequestedAPIVersion(r.URL.Path) + if v == nil { + return errors.New("couldn't get a valid version from the requested path"), nil, http.StatusBadRequest + } + if v.Major < 4 { + if user.PrivLevel < route.PrivLevel { + return errors.New("forbidden"), nil, http.StatusForbidden + } + } else { + if !cfg.RoleBasedPermissions { + if user.PrivLevel < route.PrivLevel { + return errors.New("forbidden"), nil, http.StatusForbidden + } + } else { + missingPerms := user.MissingPermissions(route.Permissions...) + if len(missingPerms) != 0 { + msg := strings.Join(missingPerms, ", ") + return fmt.Errorf("missing required Permissions: %s", msg), nil, http.StatusForbidden + } + } } api.AddUserToReq(r, user) From 62ff40a08e53afd8405ceb35cecff2fdd722df36 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Tue, 19 Apr 2022 09:30:53 -0600 Subject: [PATCH 12/18] remove priv level from backend config --- docs/source/admin/traffic_ops.rst | 1 - traffic_ops/app/conf/backends.conf | 2 -- traffic_ops/app/conf/production/backends.conf | 2 -- .../traffic_ops_golang/config/config.go | 1 - .../traffic_ops_golang/routing/routing.go | 22 +++++-------------- 5 files changed, 5 insertions(+), 23 deletions(-) diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index 1f7b08ee12..95cf912af4 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -603,7 +603,6 @@ This file deals with the configuration parameters of running Traffic Ops as a re :routeId: The integral identifier for the new route being added. :hosts: An array of the hosts and ports where the request (if matched) needs to be forwarded to, for example, `cdn-foo-backend-service-host:9090`. :insecure: A boolean specifying whether or not to enable `InsecureSkipVerify`. This is an optional parameter, defaulting to `false` when not present. - :privLevel: An integer designating the minimum privilege level required to use this API route. :permissions: An array of permissions(strings) specifying the permissions required by the user to use this API route. :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, `"alg": "roundrobin"`. Currently, only `roundrobin` is supported (which is also the default if nothing is specified) by Traffic Ops. diff --git a/traffic_ops/app/conf/backends.conf b/traffic_ops/app/conf/backends.conf index 715bc3718a..133e140d2c 100644 --- a/traffic_ops/app/conf/backends.conf +++ b/traffic_ops/app/conf/backends.conf @@ -8,7 +8,6 @@ "localhost:8445" ], "insecure": true, - "privLevel": 10, "permissions": [ "CDN:READ", "CDN:WRITE" @@ -26,7 +25,6 @@ "localhost:8445" ], "insecure": true, - "privLevel": 10, "permissions": [ "CDN:READ", "CDN:WRITE" diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf index 715bc3718a..133e140d2c 100644 --- a/traffic_ops/app/conf/production/backends.conf +++ b/traffic_ops/app/conf/production/backends.conf @@ -8,7 +8,6 @@ "localhost:8445" ], "insecure": true, - "privLevel": 10, "permissions": [ "CDN:READ", "CDN:WRITE" @@ -26,7 +25,6 @@ "localhost:8445" ], "insecure": true, - "privLevel": 10, "permissions": [ "CDN:READ", "CDN:WRITE" diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index 79d9fb3ffb..15aecb8a8c 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -48,7 +48,6 @@ type BackendRoute struct { ID int `json:"routeId"` Insecure bool `json:"insecure"` Permissions []string `json:"permissions"` - PrivLevel int `json:"privLevel"` Index int } diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 7288a40f21..4a22481280 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -374,27 +374,15 @@ func HandleBackendRoute(cfg *config.Config, route config.BackendRoute, w http.Re if v == nil { return errors.New("couldn't get a valid version from the requested path"), nil, http.StatusBadRequest } - if v.Major < 4 { - if user.PrivLevel < route.PrivLevel { - return errors.New("forbidden"), nil, http.StatusForbidden - } - } else { - if !cfg.RoleBasedPermissions { - if user.PrivLevel < route.PrivLevel { - return errors.New("forbidden"), nil, http.StatusForbidden - } - } else { - missingPerms := user.MissingPermissions(route.Permissions...) - if len(missingPerms) != 0 { - msg := strings.Join(missingPerms, ", ") - return fmt.Errorf("missing required Permissions: %s", msg), nil, http.StatusForbidden - } + if cfg.RoleBasedPermissions { + missingPerms := user.MissingPermissions(route.Permissions...) + if len(missingPerms) != 0 { + msg := strings.Join(missingPerms, ", ") + return fmt.Errorf("missing required Permissions: %s", msg), nil, http.StatusForbidden } } api.AddUserToReq(r, user) - var params []string - inf, userErr, sysErr, errCode = api.NewInfo(r, params, nil) if userErr != nil || sysErr != nil { return userErr, sysErr, errCode From 889c04d94b7e65e8f41148e313122a4dee6a2284 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 27 Apr 2022 11:20:56 -0600 Subject: [PATCH 13/18] code review first pass --- CHANGELOG.md | 2 +- docs/source/admin/traffic_ops.rst | 4 +-- traffic_ops/app/conf/backends.conf | 2 +- traffic_ops/app/conf/production/backends.conf | 30 ++++++------------- .../traffic_ops_golang/config/config.go | 6 ++++ .../routing/middleware/wrappers.go | 2 +- .../traffic_ops_golang/routing/routing.go | 18 +++-------- .../traffic_ops_golang/traffic_ops_golang.go | 8 ++--- 8 files changed, 28 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c0f56f05..da2a501690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Replaces all Traffic Portal Tenant select boxes with a novel tree select box [#6427](https://github.com/apache/trafficcontrol/issues/6427). - Traffic Monitor: Add support for `access.log` to TM. - Added functionality for login to provide a Bearer token and for that token to be later used for authorization. -- [Traffic Ops] Added support for backend configurations so that Traffic Ops can be run as a Service Oriented architecture(SOA) product [#6754](https://github.com/apache/trafficcontrol/pull/6754). +- [Traffic Ops] Added support for backend configurations so that Traffic Ops can act as a reverse proxy for these services [#6754](https://github.com/apache/trafficcontrol/pull/6754). - [Traffic Ops | Traffic Go Clients | T3C] Add additional timestamp fields to server for queuing and dequeueing config and revalidate updates. ### Fixed diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index 95cf912af4..25060ced93 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -602,8 +602,8 @@ This file deals with the configuration parameters of running Traffic Ops as a re :method: The HTTP method for the above mentioned path, for example, `GET` or `PUT`. :routeId: The integral identifier for the new route being added. :hosts: An array of the hosts and ports where the request (if matched) needs to be forwarded to, for example, `cdn-foo-backend-service-host:9090`. - :insecure: A boolean specifying whether or not to enable `InsecureSkipVerify`. This is an optional parameter, defaulting to `false` when not present. - :permissions: An array of permissions(strings) specifying the permissions required by the user to use this API route. + :insecure: A boolean specifying whether or not TO should verify the backend server's certificate chain and host name. This is not recommended for production use. This is an optional parameter, defaulting to `false` when not present. + :permissions: An array of permissions (strings) specifying the permissions required by the user to use this API route. :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, `"alg": "roundrobin"`. Currently, only `roundrobin` is supported (which is also the default if nothing is specified) by Traffic Ops. Example backends.conf diff --git a/traffic_ops/app/conf/backends.conf b/traffic_ops/app/conf/backends.conf index 133e140d2c..160835347a 100644 --- a/traffic_ops/app/conf/backends.conf +++ b/traffic_ops/app/conf/backends.conf @@ -35,4 +35,4 @@ } } ] -} \ No newline at end of file +} diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf index 133e140d2c..de176fcaf0 100644 --- a/traffic_ops/app/conf/production/backends.conf +++ b/traffic_ops/app/conf/production/backends.conf @@ -1,38 +1,26 @@ { "routes": [ { - "path": "/api/4.0/foo", - "method": "GET", - "hosts": [ - "localhost:8444", - "localhost:8445" - ], + "path": "", + "method": "", + "hosts": [], "insecure": true, - "permissions": [ - "CDN:READ", - "CDN:WRITE" - ], + "permissions": [], "routeId": 123456, "opts": { "alg": "roundrobin" } }, { - "path": "/api/4.0/foos", - "method": "GET", - "hosts": [ - "localhost:8444", - "localhost:8445" - ], + "path": "", + "method": "", + "hosts": [], "insecure": true, - "permissions": [ - "CDN:READ", - "CDN:WRITE" - ], + "permissions": [], "routeId": 123457, "opts": { "alg": "roundrobin" } } ] -} \ No newline at end of file +} diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index 15aecb8a8c..830e992c64 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -316,6 +316,12 @@ func LoadBackendConfig(backendConfigPath string) (BackendConfig, error) { if err != nil { return BackendConfig{}, fmt.Errorf("unmarshalling '%s': %v", backendConfigPath, err) } + //ToDo: do more validation here + for _, r := range cfg.Routes { + if r.Opts.Algorithm != "" && r.Opts.Algorithm != "roundrobin" { + return cfg, errors.New("algorithm can only be roundrobin or blank") + } + } return cfg, nil } diff --git a/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go b/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go index a4a9aae66b..57ed0bb520 100644 --- a/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go +++ b/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go @@ -212,7 +212,7 @@ func WrapAccessLog(secret string, h http.Handler) http.HandlerFunc { imsType = IMSMISS } } - log.EventfRaw(`%s - %s [%s] "%v %v?%v %s" %v %v %v "%v" %v %s`, r.RemoteAddr, user, time.Now().Format(AccessLogTimeFormat), r.Method, r.URL.Path, r.URL.RawQuery, r.Proto, iw.Code, iw.ByteCount, int(time.Now().Sub(start)/time.Millisecond), r.UserAgent(), r.Header.Get(RouteID), imsType) + log.EventfRaw(`%s - %s [%s] "%v %v?%v %s" %v %v %v "%v" %v %s`, r.RemoteAddr, user, time.Now().Format(AccessLogTimeFormat), r.Method, r.URL.Path, r.URL.RawQuery, r.Proto, iw.Code, iw.ByteCount, int(time.Now().Sub(start)/time.Millisecond), r.UserAgent(), r.Context().Value(RouteID), imsType) }() h.ServeHTTP(iw, r) } diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 4a22481280..5265b2ec87 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -297,8 +297,8 @@ func Handler( } routeCtx := context.WithValue(ctx, api.PathParamsKey, params) + routeCtx = context.WithValue(routeCtx, middleware.RouteID, strconv.Itoa(compiledRoute.ID)) r = r.WithContext(routeCtx) - r.Header.Add(middleware.RouteID, strconv.Itoa(compiledRoute.ID)) compiledRoute.Handler(w, r) return } @@ -321,14 +321,8 @@ func Handler( Host: host, Scheme: cfg.URL.Scheme, }) - if backendRoute.Insecure { - rp.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - } else { - rp.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{}, - } + rp.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: backendRoute.Insecure}, } rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { api.HandleErr(w, r, nil, http.StatusInternalServerError, nil, err) @@ -336,8 +330,8 @@ func Handler( } routeCtx := context.WithValue(ctx, api.DBContextKey, db) routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) + routeCtx = context.WithValue(routeCtx, middleware.RouteID, strconv.Itoa(backendRoute.ID)) r = r.WithContext(routeCtx) - r.Header.Add(middleware.RouteID, strconv.Itoa(backendRoute.ID)) userErr, sysErr, code := HandleBackendRoute(cfg, backendRoute, w, r) if userErr != nil || sysErr != nil { h2 := middleware.WrapAccessLog(cfg.Secrets[0], middleware.BackendErrorHandler(code, userErr, sysErr)) @@ -370,10 +364,6 @@ func HandleBackendRoute(cfg *config.Config, route config.BackendRoute, w http.Re if userErr != nil || sysErr != nil { return userErr, sysErr, errCode } - v := api.GetRequestedAPIVersion(r.URL.Path) - if v == nil { - return errors.New("couldn't get a valid version from the requested path"), nil, http.StatusBadRequest - } if cfg.RoleBasedPermissions { missingPerms := user.MissingPermissions(route.Permissions...) if len(missingPerms) != 0 { diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go index b7d49728ea..8a204f37b3 100644 --- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go +++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go @@ -225,8 +225,8 @@ func main() { } else { file.Close() } - - if err := http.ListenAndServeTLS(server.Addr, cfg.CertPath, cfg.KeyPath, mux); err != nil { + server.Handler = mux + if err := server.ListenAndServeTLS(cfg.CertPath, cfg.KeyPath); err != nil { log.Errorf("stopping server: %v\n", err) os.Exit(1) } @@ -244,7 +244,7 @@ func main() { continuousProfile(&profiling, &profilingLocation, cfg.Version) } - reloadProfilingConfig := func() { + reloadProfilingAndBackendConfig := func() { setNewProfilingInfo(*configFileName, &profiling, &profilingLocation, cfg.Version) backendConfig, err = setNewBackendConfig(backendConfigFileName) if err != nil { @@ -254,7 +254,7 @@ func main() { routing.SetBackendConfig(backendConfig) } } - signalReloader(unix.SIGHUP, reloadProfilingConfig) + signalReloader(unix.SIGHUP, reloadProfilingAndBackendConfig) } func setupTrafficVault(riakConfigFileName string, cfg *config.Config) trafficvault.TrafficVault { From 2bff46fbdc3bccc6328d03cb1ee09fe69834e6a0 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 27 Apr 2022 16:15:39 -0600 Subject: [PATCH 14/18] code review fixes final --- docs/source/admin/traffic_ops.rst | 11 +++- traffic_ops/app/conf/backends.conf | 34 ++++++++--- traffic_ops/app/conf/production/backends.conf | 30 +++++++-- .../traffic_ops_golang/config/config.go | 15 ++++- .../traffic_ops_golang/routing/routing.go | 61 ++++++++++++------- .../traffic_ops_golang/traffic_ops_golang.go | 17 ++---- 6 files changed, 117 insertions(+), 51 deletions(-) diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index 25060ced93..9650791fd7 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -19,7 +19,7 @@ *********** Traffic Ops *********** -Traffic Ops is quite possible the single most complex and most important Traffic Control component. It has many different configuration options that affect a wide range of other components and their interactions. +Traffic Ops is quite possibly the single most complex and most important Traffic Control component. It has many different configuration options that affect a wide range of other components and their interactions. .. _to-install: @@ -598,10 +598,15 @@ This file deals with the configuration parameters of running Traffic Ops as a re :routes: This is an array of options to configure Traffic Ops to forward requests of specified types to the appropriate backends. - :path: The endpoint that will be served by the backend, for example, `api/4.0/foo`. + :path: The regex matching the endpoint that will be served by the backend, for example, `^api/4.0/foo?$`. :method: The HTTP method for the above mentioned path, for example, `GET` or `PUT`. :routeId: The integral identifier for the new route being added. - :hosts: An array of the hosts and ports where the request (if matched) needs to be forwarded to, for example, `cdn-foo-backend-service-host:9090`. + :hosts: An array of the host object, which specifies the protocol, hostname and port where the request (if matched) needs to be forwarded to. + + :protocol: The protocol/scheme to be followed while forwarding the requests to the backend service. + :hostname: The hostname of the server where the backend service is running. + :port: The port (integer) on the backend server where the service is running. + :insecure: A boolean specifying whether or not TO should verify the backend server's certificate chain and host name. This is not recommended for production use. This is an optional parameter, defaulting to `false` when not present. :permissions: An array of permissions (strings) specifying the permissions required by the user to use this API route. :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, `"alg": "roundrobin"`. Currently, only `roundrobin` is supported (which is also the default if nothing is specified) by Traffic Ops. diff --git a/traffic_ops/app/conf/backends.conf b/traffic_ops/app/conf/backends.conf index 160835347a..2401a44996 100644 --- a/traffic_ops/app/conf/backends.conf +++ b/traffic_ops/app/conf/backends.conf @@ -1,16 +1,23 @@ { "routes": [ { - "path": "/api/4.0/foo", + "path": "^/api/4.0/foo?$", "method": "GET", "hosts": [ - "localhost:8444", - "localhost:8445" + { + "protocol": "https", + "hostname": "localhost", + "port": 8444 + }, + { + "protocol": "https", + "hostname": "localhost", + "port": 8445 + } ], "insecure": true, "permissions": [ - "CDN:READ", - "CDN:WRITE" + "CDN:READ" ], "routeId": 123456, "opts": { @@ -18,16 +25,23 @@ } }, { - "path": "/api/4.0/foos", + "path": "^/api/4.0/foos?$", "method": "GET", "hosts": [ - "localhost:8444", - "localhost:8445" + { + "protocol": "https", + "hostname": "localhost", + "port": 8444 + }, + { + "protocol": "https", + "hostname": "localhost", + "port": 8445 + } ], "insecure": true, "permissions": [ - "CDN:READ", - "CDN:WRITE" + "CDN:READ" ], "routeId": 123457, "opts": { diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf index de176fcaf0..d959996559 100644 --- a/traffic_ops/app/conf/production/backends.conf +++ b/traffic_ops/app/conf/production/backends.conf @@ -3,10 +3,21 @@ { "path": "", "method": "", - "hosts": [], + "hosts": [ + { + "protocol": "", + "hostname": "", + "port": 8444 + }, + { + "protocol": "", + "hostname": "", + "port": 8445 + } + ], "insecure": true, "permissions": [], - "routeId": 123456, + "routeId": 0, "opts": { "alg": "roundrobin" } @@ -14,10 +25,21 @@ { "path": "", "method": "", - "hosts": [], + "hosts": [ + { + "protocol": "", + "hostname": "", + "port": 8444, + }, + { + "protocol": "", + "hostname": "", + "port": 8445 + } + ], "insecure": true, "permissions": [], - "routeId": 123457, + "routeId": 1, "opts": { "alg": "roundrobin" } diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index 830e992c64..6ba09ac6c6 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -40,10 +40,16 @@ type Options struct { Algorithm string `json:"alg"` } +type Host struct { + Protocol string `json:"protocol"` + Hostname string `json:"hostname"` + Port int `json:"port"` +} + type BackendRoute struct { Path string `json:"path"` Method string `json:"method"` - Hosts []string `json:"hosts"` + Hosts []Host `json:"hosts"` Opts Options `json:"opts"` ID int `json:"routeId"` Insecure bool `json:"insecure"` @@ -316,11 +322,16 @@ func LoadBackendConfig(backendConfigPath string) (BackendConfig, error) { if err != nil { return BackendConfig{}, fmt.Errorf("unmarshalling '%s': %v", backendConfigPath, err) } - //ToDo: do more validation here for _, r := range cfg.Routes { if r.Opts.Algorithm != "" && r.Opts.Algorithm != "roundrobin" { return cfg, errors.New("algorithm can only be roundrobin or blank") } + for _, h := range r.Hosts { + rawURL := h.Protocol + "://" + h.Hostname + ":" + strconv.Itoa(h.Port) + if _, err = url.ParseRequestURI(rawURL); err != nil { + return cfg, fmt.Errorf("couldn't convert host info into a valid URI: %v", err) + } + } } return cfg, nil } diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 5265b2ec87..c2e57b3dc3 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -51,24 +51,26 @@ import ( // RoutePrefix is a prefix that all API routes must match. const RoutePrefix = "^api" // TODO config? -// BackendConfig stores the current backend config supplied to traffic ops. -var BackendConfig config.BackendConfig +type backendConfigSynced struct { + cfg config.BackendConfig + *sync.RWMutex +} -// Mutex is a mutex for safely reading/ writing to BackendConfig. -var Mutex = sync.RWMutex{} +// backendCfg stores the current backend config supplied to traffic ops. +var backendCfg = backendConfigSynced{RWMutex: &sync.RWMutex{}} // GetBackendConfig returns the current BackendConfig. func GetBackendConfig() config.BackendConfig { - Mutex.RLock() - defer Mutex.RUnlock() - return BackendConfig + backendCfg.RLock() + defer backendCfg.RUnlock() + return backendCfg.cfg } // SetBackendConfig sets the BackendConfig to the value supplied. func SetBackendConfig(backendConfig config.BackendConfig) { - Mutex.Lock() - defer Mutex.Unlock() - BackendConfig = backendConfig + backendCfg.RLock() + defer backendCfg.RUnlock() + backendCfg.cfg = backendConfig } // A Route defines an association with a client request and a handler for that @@ -106,12 +108,11 @@ func (r *Route) SetMiddleware(authBase middleware.AuthBase, requestTimeout time. // ServerData ... type ServerData struct { config.Config - DB *sqlx.DB - Profiling *bool // Yes this is a field in the config but we want to live reload this value and NOT the entire config - Plugins plugin.Plugins - TrafficVault trafficvault.TrafficVault - BackendConfig config.BackendConfig - Mux *http.ServeMux + DB *sqlx.DB + Profiling *bool // Yes this is a field in the config but we want to live reload this value and NOT the entire config + Plugins plugin.Plugins + TrafficVault trafficvault.TrafficVault + Mux *http.ServeMux } // CompiledRoute ... @@ -310,7 +311,26 @@ func Handler( var backendRouteHandled bool backendConfig := GetBackendConfig() for i, backendRoute := range backendConfig.Routes { - if backendRoute.Path == r.URL.Path && backendRoute.Method == r.Method { + var params []string + routeParams := map[string]string{} + if backendRoute.Method == r.Method { + for open := strings.Index(backendRoute.Path, "{"); open > 0; open = strings.Index(backendRoute.Path, "{") { + close := strings.Index(backendRoute.Path, "}") + if close < 0 { + panic("malformed route") + } + param := backendRoute.Path[open+1 : close] + params = append(params, param) + backendRoute.Path = backendRoute.Path[:open] + `([^/]+)` + backendRoute.Path[close+1:] + } + regex := regexp.MustCompile(backendRoute.Path) + match := regex.FindStringSubmatch(requested) + if len(match) == 0 { + continue + } + for i, v := range params { + routeParams[v] = match[i+1] + } if backendRoute.Opts.Algorithm == "" || backendRoute.Opts.Algorithm == "roundrobin" { index := backendRoute.Index % len(backendRoute.Hosts) host := backendRoute.Hosts[index] @@ -318,8 +338,8 @@ func Handler( backendConfig.Routes[i] = backendRoute backendRouteHandled = true rp := httputil.NewSingleHostReverseProxy(&url.URL{ - Host: host, - Scheme: cfg.URL.Scheme, + Host: host.Hostname + ":" + strconv.Itoa(host.Port), + Scheme: host.Protocol, }) rp.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: backendRoute.Insecure}, @@ -329,7 +349,7 @@ func Handler( return } routeCtx := context.WithValue(ctx, api.DBContextKey, db) - routeCtx = context.WithValue(routeCtx, api.PathParamsKey, map[string]string{}) + routeCtx = context.WithValue(routeCtx, api.PathParamsKey, routeParams) routeCtx = context.WithValue(routeCtx, middleware.RouteID, strconv.Itoa(backendRoute.ID)) r = r.WithContext(routeCtx) userErr, sysErr, code := HandleBackendRoute(cfg, backendRoute, w, r) @@ -432,7 +452,6 @@ func RegisterRoutes(d ServerData) error { compiledRoutes := CompileRoutes(routes) getReqID := nextReqIDGetter() - SetBackendConfig(d.BackendConfig) d.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { Handler(compiledRoutes, versions, catchall, d.DB, &d.Config, getReqID, d.Plugins, d.TrafficVault, w, r) }) diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go index 8a204f37b3..04ceed6fc0 100644 --- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go +++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go @@ -169,13 +169,14 @@ func main() { var backendConfig config.BackendConfig if *backendConfigFileName != "" { backendConfig, err = config.LoadBackendConfig(*backendConfigFileName) + routing.SetBackendConfig(backendConfig) if err != nil { log.Errorf("error loading backend config: %v", err) } } mux := http.NewServeMux() - d := routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault, BackendConfig: backendConfig, Mux: mux} + d := routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault, Mux: mux} if err := routing.RegisterRoutes(d); err != nil { log.Errorf("registering routes: %v\n", err) os.Exit(1) @@ -246,11 +247,10 @@ func main() { reloadProfilingAndBackendConfig := func() { setNewProfilingInfo(*configFileName, &profiling, &profilingLocation, cfg.Version) - backendConfig, err = setNewBackendConfig(backendConfigFileName) + backendConfig, err = getNewBackendConfig(backendConfigFileName) if err != nil { log.Errorf("could not reload backend config: %v", err) } else { - d.BackendConfig = backendConfig routing.SetBackendConfig(backendConfig) } } @@ -312,11 +312,12 @@ func setupTrafficVault(riakConfigFileName string, cfg *config.Config) trafficvau return &disabled.Disabled{} } -func setNewBackendConfig(backendConfigFileName *string) (config.BackendConfig, error) { +func getNewBackendConfig(backendConfigFileName *string) (config.BackendConfig, error) { if backendConfigFileName == nil { return config.BackendConfig{}, errors.New("no backend config filename") } - backendConfig, err := reloadBackendConfig(*backendConfigFileName) + log.Infoln("setting new backend config to %s", *backendConfigFileName) + backendConfig, err := config.LoadBackendConfig(*backendConfigFileName) if err != nil { log.Errorf("error reloading config: %v", err) return backendConfig, err @@ -381,12 +382,6 @@ func reloadProfilingInfo(configFileName string) (bool, string, error) { return cfg.ProfilingEnabled, profilingLocation, nil } -func reloadBackendConfig(backendConfigFileName string) (config.BackendConfig, error) { - log.Infoln("reload backend config") - backendConfig, err := config.LoadBackendConfig(backendConfigFileName) - return backendConfig, err -} - func continuousProfile(profiling *bool, profilingDir *string, version string) { if *profiling && *profilingDir != "" { go func() { From 0361219b6fe2673f3c5a249a895770a10dd98ff5 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 27 Apr 2022 16:22:46 -0600 Subject: [PATCH 15/18] change log call --- traffic_ops/traffic_ops_golang/traffic_ops_golang.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go index 04ceed6fc0..42a3acf45e 100644 --- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go +++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go @@ -316,7 +316,7 @@ func getNewBackendConfig(backendConfigFileName *string) (config.BackendConfig, e if backendConfigFileName == nil { return config.BackendConfig{}, errors.New("no backend config filename") } - log.Infoln("setting new backend config to %s", *backendConfigFileName) + log.Infof("setting new backend config to %s", *backendConfigFileName) backendConfig, err := config.LoadBackendConfig(*backendConfigFileName) if err != nil { log.Errorf("error reloading config: %v", err) From feb07ab6c82058bd40320e6ec7964025ef365823 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Thu, 28 Apr 2022 22:23:41 -0600 Subject: [PATCH 16/18] change mutex type --- traffic_ops/traffic_ops_golang/routing/routing.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index c2e57b3dc3..eb5ac96b3c 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -68,8 +68,8 @@ func GetBackendConfig() config.BackendConfig { // SetBackendConfig sets the BackendConfig to the value supplied. func SetBackendConfig(backendConfig config.BackendConfig) { - backendCfg.RLock() - defer backendCfg.RUnlock() + backendCfg.Lock() + defer backendCfg.Unlock() backendCfg.cfg = backendConfig } From ae2cbf980d379cd2d61dd6ceedf9c39a33cba84c Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 2 May 2022 16:20:02 -0600 Subject: [PATCH 17/18] addressing code review comments --- docs/source/admin/traffic_ops.rst | 10 ++-- traffic_ops/app/conf/production/backends.conf | 47 +------------------ .../traffic_ops_golang/config/config.go | 4 ++ 3 files changed, 9 insertions(+), 52 deletions(-) diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index 93ef5573d5..d8a266ceae 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -590,16 +590,14 @@ This file sets authentication options for connections to Riak when used as the T .. impl-detail:: The name of this file is derived from the current database used in the implementation of Traffic Vault - `Riak KV `_. -.. _backends.conf: - backends.conf """"""""""""" This file deals with the configuration parameters of running Traffic Ops as a reverse proxy for certain endpoints that need to be served externally by other backend services. It is a JSON-format set of options and their respective values. `traffic_ops_golang`_ will use whatever file is specified (if any) by its :option:`--backendcfg` option. The keys of the file are described below. :routes: This is an array of options to configure Traffic Ops to forward requests of specified types to the appropriate backends. - :path: The regex matching the endpoint that will be served by the backend, for example, `^api/4.0/foo?$`. - :method: The HTTP method for the above mentioned path, for example, `GET` or `PUT`. + :path: The regex matching the endpoint that will be served by the backend, for example, :regexp:`^api/4.0/foo?$`. + :method: The HTTP method for the above mentioned path, for example, ``GET`` or ``PUT``. :routeId: The integral identifier for the new route being added. :hosts: An array of the host object, which specifies the protocol, hostname and port where the request (if matched) needs to be forwarded to. @@ -607,9 +605,9 @@ This file deals with the configuration parameters of running Traffic Ops as a re :hostname: The hostname of the server where the backend service is running. :port: The port (integer) on the backend server where the service is running. - :insecure: A boolean specifying whether or not TO should verify the backend server's certificate chain and host name. This is not recommended for production use. This is an optional parameter, defaulting to `false` when not present. + :insecure: A boolean specifying whether or not TO should verify the backend server's certificate chain and host name. This is not recommended for production use. This is an optional parameter, defaulting to ``false`` when not present. :permissions: An array of permissions (strings) specifying the permissions required by the user to use this API route. - :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, `"alg": "roundrobin"`. Currently, only `roundrobin` is supported (which is also the default if nothing is specified) by Traffic Ops. + :opts: A collection of key value pairs to control how the requests should be forwarded/ handled, for example, ``"alg": "roundrobin"``. Currently, only ``roundrobin`` is supported (which is also the default if nothing is specified) by Traffic Ops. Example backends.conf ''''''''''''''''''''' diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf index d959996559..4feaf1db51 100644 --- a/traffic_ops/app/conf/production/backends.conf +++ b/traffic_ops/app/conf/production/backends.conf @@ -1,48 +1,3 @@ { - "routes": [ - { - "path": "", - "method": "", - "hosts": [ - { - "protocol": "", - "hostname": "", - "port": 8444 - }, - { - "protocol": "", - "hostname": "", - "port": 8445 - } - ], - "insecure": true, - "permissions": [], - "routeId": 0, - "opts": { - "alg": "roundrobin" - } - }, - { - "path": "", - "method": "", - "hosts": [ - { - "protocol": "", - "hostname": "", - "port": 8444, - }, - { - "protocol": "", - "hostname": "", - "port": 8445 - } - ], - "insecure": true, - "permissions": [], - "routeId": 1, - "opts": { - "alg": "roundrobin" - } - } - ] + "routes": [], } diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index 6ba09ac6c6..521da1d9bd 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -36,16 +36,19 @@ import ( "github.com/apache/trafficcontrol/lib/go-util" ) +// Options is a structure used to hold the route configuration options that can be supplied for the backend routes. type Options struct { Algorithm string `json:"alg"` } +// Host is a structure that holds the host info for the backend route. type Host struct { Protocol string `json:"protocol"` Hostname string `json:"hostname"` Port int `json:"port"` } +// BackendRoute holds all the information about a configured route, for which Traffic Ops serves as a reverse proxy. type BackendRoute struct { Path string `json:"path"` Method string `json:"method"` @@ -57,6 +60,7 @@ type BackendRoute struct { Index int } +// BackendConfig is a structure that holds the configuration supplied to Traffic Ops, which makes it act as a reverse proxy to the specified routes. type BackendConfig struct { Routes []BackendRoute `json:"routes"` } From 6e3bdb00ffe51185687c35f76b1d2a6ad234e568 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 9 May 2022 11:48:53 -0600 Subject: [PATCH 18/18] change regex handling --- docs/source/admin/traffic_ops.rst | 2 +- traffic_ops/traffic_ops_golang/routing/routing.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst index d8a266ceae..6ea3cbf479 100644 --- a/docs/source/admin/traffic_ops.rst +++ b/docs/source/admin/traffic_ops.rst @@ -596,7 +596,7 @@ This file deals with the configuration parameters of running Traffic Ops as a re :routes: This is an array of options to configure Traffic Ops to forward requests of specified types to the appropriate backends. - :path: The regex matching the endpoint that will be served by the backend, for example, :regexp:`^api/4.0/foo?$`. + :path: The regex matching the endpoint that will be served by the backend, for example, :regexp:`^/api/4.0/foo?$`. :method: The HTTP method for the above mentioned path, for example, ``GET`` or ``PUT``. :routeId: The integral identifier for the new route being added. :hosts: An array of the host object, which specifies the protocol, hostname and port where the request (if matched) needs to be forwarded to. diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index 1e55d5ef1a..c3ca1adcb4 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -324,7 +324,7 @@ func Handler( backendRoute.Path = backendRoute.Path[:open] + `([^/]+)` + backendRoute.Path[close+1:] } regex := regexp.MustCompile(backendRoute.Path) - match := regex.FindStringSubmatch(requested) + match := regex.FindStringSubmatch(r.URL.Path) if len(match) == 0 { continue }