mirror of
https://github.com/rancher/steve.git
synced 2025-04-28 03:10:32 +00:00
182 lines
4.8 KiB
Go
182 lines
4.8 KiB
Go
package proxy
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/rancher/wrangler/pkg/kubeconfig"
|
|
"github.com/sirupsen/logrus"
|
|
"k8s.io/apimachinery/pkg/util/proxy"
|
|
"k8s.io/apiserver/pkg/authentication/user"
|
|
"k8s.io/apiserver/pkg/endpoints/request"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/client-go/transport"
|
|
)
|
|
|
|
// Mostly copied from "kubectl proxy" code
|
|
func HandlerFromConfig(prefix, kubeConfig string) (http.Handler, error) {
|
|
loader := kubeconfig.GetInteractiveClientConfig(kubeConfig)
|
|
cfg, err := loader.ClientConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return Handler(prefix, cfg)
|
|
}
|
|
|
|
func ImpersonatingHandler(prefix string, cfg *rest.Config) http.Handler {
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
impersonate(rw, req, prefix, cfg)
|
|
})
|
|
}
|
|
|
|
func setupUserAuth(req *http.Request, user user.Info, cfg *rest.Config) (*rest.Config, bool) {
|
|
authed := true
|
|
for _, group := range user.GetGroups() {
|
|
if group == "system:unauthenticated" && strings.HasPrefix(req.Header.Get("Authorization"), "Bearer ") {
|
|
cfg := rest.CopyConfig(cfg)
|
|
cfg.Username = ""
|
|
cfg.Password = ""
|
|
cfg.BearerTokenFile = ""
|
|
cfg.TLSClientConfig.CertFile = ""
|
|
cfg.TLSClientConfig.CertData = nil
|
|
cfg.ExecProvider = nil
|
|
cfg.AuthConfigPersister = nil
|
|
cfg.Impersonate = rest.ImpersonationConfig{}
|
|
|
|
cfg.BearerToken = strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
|
|
return cfg, true
|
|
} else if group == "system:unauthenticated" {
|
|
authed = false
|
|
}
|
|
}
|
|
|
|
cfg = rest.CopyConfig(cfg)
|
|
cfg.Impersonate.UserName = user.GetName()
|
|
cfg.Impersonate.Groups = user.GetGroups()
|
|
cfg.Impersonate.Extra = user.GetExtra()
|
|
return cfg, authed
|
|
}
|
|
|
|
func impersonate(rw http.ResponseWriter, req *http.Request, prefix string, cfg *rest.Config) {
|
|
user, ok := request.UserFrom(req.Context())
|
|
if !ok {
|
|
http.Error(rw, "not authorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
cfg, authed := setupUserAuth(req, user, cfg)
|
|
// we want to send a 401, not a 403 for unauthed API requests to make the UI happier
|
|
if !authed && strings.HasPrefix(req.URL.Path, "/api") {
|
|
http.Error(rw, "not authorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
handler, err := Handler(prefix, cfg)
|
|
if err != nil {
|
|
logrus.Errorf("failed to impersonate %v for proxy: %v", user, err)
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
req = req.Clone(req.Context())
|
|
for k := range req.Header {
|
|
if strings.HasPrefix(k, "Impersonate-") {
|
|
delete(req.Header, k)
|
|
}
|
|
}
|
|
handler.ServeHTTP(rw, req)
|
|
}
|
|
|
|
// Mostly copied from "kubectl proxy" code
|
|
func Handler(prefix string, cfg *rest.Config) (http.Handler, error) {
|
|
host := cfg.Host
|
|
if !strings.HasSuffix(host, "/") {
|
|
host = host + "/"
|
|
}
|
|
target, err := url.Parse(host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
transport, err := rest.TransportFor(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
upgradeTransport, err := makeUpgradeTransport(cfg, transport)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
proxy := proxy.NewUpgradeAwareHandler(target, transport, false, false, er)
|
|
proxy.UpgradeTransport = upgradeTransport
|
|
proxy.UseRequestLocation = true
|
|
|
|
handler := http.Handler(proxy)
|
|
|
|
if len(target.Path) > 1 {
|
|
handler = prependPath(target.Path[:len(target.Path)-1], handler)
|
|
}
|
|
|
|
if len(prefix) > 2 {
|
|
handler = stripLeaveSlash(prefix, handler)
|
|
}
|
|
|
|
return proxyHeaders(handler), nil
|
|
}
|
|
|
|
func proxyHeaders(handler http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
req.Header.Del("Authorization")
|
|
if req.Header.Get("X-Forwarded-Proto") == "" && req.TLS != nil {
|
|
req.Header.Set("X-Forwarded-Proto", "https")
|
|
}
|
|
handler.ServeHTTP(rw, req)
|
|
})
|
|
}
|
|
|
|
func SetHost(host string, h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
req.Host = host
|
|
h.ServeHTTP(w, req)
|
|
})
|
|
}
|
|
|
|
func prependPath(prefix string, h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if len(req.URL.Path) > 1 {
|
|
req.URL.Path = prefix + req.URL.Path
|
|
} else {
|
|
req.URL.Path = prefix
|
|
}
|
|
h.ServeHTTP(w, req)
|
|
})
|
|
}
|
|
|
|
// like http.StripPrefix, but always leaves an initial slash. (so that our
|
|
// regexps will work.)
|
|
func stripLeaveSlash(prefix string, h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
p := strings.TrimPrefix(req.URL.Path, prefix)
|
|
if len(p) > 0 && p[:1] != "/" {
|
|
p = "/" + p
|
|
}
|
|
req.URL.Path = p
|
|
h.ServeHTTP(w, req)
|
|
})
|
|
}
|
|
|
|
func makeUpgradeTransport(config *rest.Config, rt http.RoundTripper) (proxy.UpgradeRequestRoundTripper, error) {
|
|
transportConfig, err := config.TransportConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
upgrader, err := transport.HTTPWrappersForConfig(transportConfig, proxy.MirrorRequest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil
|
|
}
|