diff --git a/pkg/proxy/error.go b/pkg/proxy/error.go new file mode 100644 index 0000000..d6735c3 --- /dev/null +++ b/pkg/proxy/error.go @@ -0,0 +1,15 @@ +package proxy + +import "net/http" + +var ( + er = &errorResponder{} +) + +type errorResponder struct { +} + +func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) +} diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go new file mode 100644 index 0000000..4e1a9b4 --- /dev/null +++ b/pkg/proxy/proxy.go @@ -0,0 +1,101 @@ +package proxy + +import ( + "fmt" + "net" + "net/http" + "net/url" + "strings" + "time" + + "github.com/rancher/wrangler/pkg/kubeconfig" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/proxy" + "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) +} + +// 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, 0) + if err != nil { + return nil, err + } + + proxy := proxy.NewUpgradeAwareHandler(target, transport, false, false, er) + proxy.UpgradeTransport = upgradeTransport + proxy.UseRequestLocation = true + + if len(prefix) > 2 { + return stripLeaveSlash(prefix, proxy), nil + } + + return proxy, nil +} + +// 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) { + fmt.Println(req.Method, req.URL.Path) + p := strings.TrimPrefix(req.URL.Path, prefix) + if len(p) >= len(req.URL.Path) { + http.NotFound(w, req) + return + } + if len(p) > 0 && p[:1] != "/" { + p = "/" + p + } + req.URL.Path = p + h.ServeHTTP(w, req) + }) +} + +func makeUpgradeTransport(config *rest.Config, keepalive time.Duration) (proxy.UpgradeRequestRoundTripper, error) { + transportConfig, err := config.TransportConfig() + if err != nil { + return nil, err + } + tlsConfig, err := transport.TLSConfigFor(transportConfig) + if err != nil { + return nil, err + } + rt := utilnet.SetOldTransportDefaults(&http.Transport{ + TLSClientConfig: tlsConfig, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: keepalive, + }).DialContext, + }) + + upgrader, err := transport.HTTPWrappersForConfig(transportConfig, proxy.MirrorRequest) + if err != nil { + return nil, err + } + + return proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil +} diff --git a/pkg/server/api.go b/pkg/server/api.go index d8cc5ce..10634da 100644 --- a/pkg/server/api.go +++ b/pkg/server/api.go @@ -6,6 +6,7 @@ import ( "github.com/gorilla/mux" "github.com/rancher/naok/pkg/accesscontrol" "github.com/rancher/naok/pkg/attributes" + k8sproxy "github.com/rancher/naok/pkg/proxy" "github.com/rancher/naok/pkg/schemas" "github.com/rancher/norman/pkg/api" "github.com/rancher/norman/pkg/store/proxy" @@ -13,9 +14,14 @@ import ( "github.com/rancher/norman/pkg/types" "github.com/rancher/norman/pkg/urlbuilder" "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/client-go/rest" ) -func newAPIServer(cf proxy.ClientGetter, as *accesscontrol.AccessStore, sf schemas.SchemaFactory) http.Handler { +func newAPIServer(cfg *rest.Config, cf proxy.ClientGetter, as *accesscontrol.AccessStore, sf schemas.SchemaFactory) (http.Handler, error) { + var ( + err error + ) + a := &apiServer{ Router: mux.NewRouter(), cf: cf, @@ -23,10 +29,15 @@ func newAPIServer(cf proxy.ClientGetter, as *accesscontrol.AccessStore, sf schem sf: sf, server: api.NewAPIServer(), } + + a.Router.NotFoundHandler, err = k8sproxy.Handler("/", cfg) + if err != nil { + return nil, err + } + a.Router.StrictSlash(true) a.server.AccessControl = accesscontrol.NewAccessControl() - a.routes() - return a + return a, a.routes() } type apiServer struct { diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 4a6ca35..fa18250 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -3,16 +3,15 @@ package server import ( "net/http" - "github.com/rancher/naok/pkg/attributes" - "github.com/gorilla/mux" + "github.com/rancher/naok/pkg/attributes" "github.com/rancher/norman/pkg/types" "k8s.io/apimachinery/pkg/runtime/schema" ) type APIFunc func(*types.APIRequest) -func (a *apiServer) routes() { +func (a *apiServer) routes() error { a.Path("/v1/{type:schemas}").Handler(a.handle(nil)) a.Path("/v1/{type:schemas}/{name}").Handler(a.handle(nil)) a.Path("/v1/{type:subscribe}").Handler(a.handle(nil)) @@ -23,6 +22,8 @@ func (a *apiServer) routes() { a.Path("/v1/apis/{group}/{version}/{resource}").Handler(a.handle(a.k8sAPI)) a.Path("/v1/apis/{group}/{version}/{resource}/{nameorns}").Handler(a.handle(a.k8sAPI)) + + return nil } func (a *apiServer) handle(apiFunc APIFunc) http.Handler {