diff --git a/pkg/auth/filter.go b/pkg/auth/filter.go index b1bb3bf8..ff5b5783 100644 --- a/pkg/auth/filter.go +++ b/pkg/auth/filter.go @@ -16,6 +16,11 @@ import ( "k8s.io/client-go/transport" ) +var ExistingContext = ToMiddleware(AuthenticatorFunc(func(req *http.Request) (user.Info, bool, error) { + user, ok := request.UserFrom(req.Context()) + return user, ok, nil +})) + type Authenticator interface { Authenticate(req *http.Request) (user.Info, bool, error) } @@ -26,15 +31,12 @@ func (a AuthenticatorFunc) Authenticate(req *http.Request) (user.Info, bool, err return a(req) } -type Middleware func(http.ResponseWriter, *http.Request, http.Handler) +type Middleware func(next http.Handler) http.Handler -func (m Middleware) Wrap(handler http.Handler) http.Handler { - if m == nil { - return handler +func (m Middleware) Chain(middleware Middleware) Middleware { + return func(next http.Handler) http.Handler { + return m(middleware(next)) } - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - m(rw, req, handler) - }) } func WebhookConfigForURL(url string) (string, error) { @@ -127,22 +129,32 @@ func (w *webhookAuth) Authenticate(req *http.Request) (user.Info, bool, error) { } func ToMiddleware(auth Authenticator) Middleware { - return func(rw http.ResponseWriter, req *http.Request, next http.Handler) { - info, ok, err := auth.Authenticate(req) - if err != nil { - rw.WriteHeader(http.StatusUnauthorized) - rw.Write([]byte(err.Error())) - return - } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + info, ok, err := auth.Authenticate(req) + if err != nil { + info = &user.DefaultInfo{ + Name: "system:cattle:error", + UID: "system:cattle:error", + Groups: []string{ + "system:unauthenticated", + "system:cattle:error", + }, + } + } else if !ok { + info = &user.DefaultInfo{ + Name: "system:unauthenticated", + UID: "system:unauthenticated", + Groups: []string{ + "system:unauthenticated", + }, + } + } - if !ok { - rw.WriteHeader(http.StatusUnauthorized) - return - } - - ctx := request.WithUser(req.Context(), info) - req = req.WithContext(ctx) - next.ServeHTTP(rw, req) + ctx := request.WithUser(req.Context(), info) + req = req.WithContext(ctx) + next.ServeHTTP(rw, req) + }) } } diff --git a/pkg/controllers/schema/schemas.go b/pkg/controllers/schema/schemas.go index f2f4431d..aca2c1bc 100644 --- a/pkg/controllers/schema/schemas.go +++ b/pkg/controllers/schema/schemas.go @@ -55,7 +55,7 @@ func Register(ctx context.Context, apiService v1.APIServiceController, ssar authorizationv1client.SelfSubjectAccessReviewInterface, schemasHandler SchemasHandler, - schemas *schema2.Collection) (init func() error) { + schemas *schema2.Collection) { h := &handler{ ctx: ctx, @@ -69,11 +69,6 @@ func Register(ctx context.Context, apiService.OnChange(ctx, "schema", h.OnChangeAPIService) crd.OnChange(ctx, "schema", h.OnChangeCRD) - - return func() error { - h.queueRefresh() - return h.refreshAll(ctx) - } } func (h *handler) OnChangeCRD(key string, crd *v1beta1.CustomResourceDefinition) (*v1beta1.CustomResourceDefinition, error) { diff --git a/pkg/dashboard/ui.go b/pkg/dashboard/ui.go deleted file mode 100644 index e3056396..00000000 --- a/pkg/dashboard/ui.go +++ /dev/null @@ -1,90 +0,0 @@ -package dashboard - -import ( - "crypto/tls" - "io" - "net/http" - "strings" - - "github.com/gorilla/mux" - "github.com/rancher/apiserver/pkg/middleware" - "github.com/rancher/apiserver/pkg/parse" - "github.com/sirupsen/logrus" -) - -var ( - insecureClient = &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } -) - -func content(uiSetting func() string) http.Handler { - return http.FileServer(http.Dir(uiSetting())) -} - -func Route(next http.Handler, uiSetting func() string) http.Handler { - uiContent := middleware.NewMiddlewareChain(middleware.Gzip, - middleware.DenyFrameOptions, - middleware.CacheMiddleware("json", "js", "css")).Handler(content(uiSetting)) - - root := mux.NewRouter() - root.UseEncodedPath() - root.Path("/dashboard").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - http.Redirect(rw, req, "/dashboard/", http.StatusFound) - }) - root.PathPrefix("/dashboard/assets").Handler(uiContent) - root.PathPrefix("/dashboard/translations").Handler(uiContent) - root.PathPrefix("/dashboard/engines-dist").Handler(uiContent) - root.Handle("/dashboard/asset-manifest.json", uiContent) - root.Handle("/dashboard/index.html", uiContent) - root.PathPrefix("/dashboard/").Handler(wrapUI(next, uiSetting)) - root.NotFoundHandler = next - - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if strings.HasPrefix(req.URL.Path, "/k8s/clusters/local") { - req.URL.Path = strings.TrimPrefix(req.URL.Path, "/k8s/clusters/local") - if req.URL.Path == "" { - req.URL.Path = "/" - } - } - root.ServeHTTP(rw, req) - }) -} - -func wrapUI(next http.Handler, uiGetter func() string) http.Handler { - return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - if parse.IsBrowser(req, true) { - path := uiGetter() - if strings.HasPrefix(path, "http") { - ui(resp, req, path) - } else { - http.ServeFile(resp, req, path) - } - } else { - next.ServeHTTP(resp, req) - } - }) -} - -func ui(resp http.ResponseWriter, req *http.Request, url string) { - if err := serveIndex(resp, req, url); err != nil { - logrus.Errorf("failed to serve UI: %v", err) - resp.WriteHeader(500) - } -} - -func serveIndex(resp http.ResponseWriter, req *http.Request, url string) error { - r, err := insecureClient.Get(url) - if err != nil { - return err - } - defer r.Body.Close() - - _, err = io.Copy(resp, r.Body) - return err -} diff --git a/pkg/schema/collection.go b/pkg/schema/collection.go index 24272540..df9b7139 100644 --- a/pkg/schema/collection.go +++ b/pkg/schema/collection.go @@ -23,6 +23,7 @@ type Factory interface { ByGVR(gvr schema.GroupVersionResource) string ByGVK(gvr schema.GroupVersionKind) string OnChange(ctx context.Context, cb func()) + AddTemplate(template ...Template) } type Collection struct { @@ -210,17 +211,19 @@ func (c *Collection) ByGVK(gvk schema.GroupVersionKind) string { return c.byGVK[gvk] } -func (c *Collection) AddTemplate(template *Template) { +func (c *Collection) AddTemplate(templates ...Template) { c.lock.RLock() defer c.lock.RUnlock() - if template.Kind != "" { - c.templates[template.Group+"/"+template.Kind] = template - } - if template.ID != "" { - c.templates[template.ID] = template - } - if template.Kind == "" && template.Group == "" && template.ID == "" { - c.templates[""] = template + for i, template := range templates { + if template.Kind != "" { + c.templates[template.Group+"/"+template.Kind] = &templates[i] + } + if template.ID != "" { + c.templates[template.ID] = &templates[i] + } + if template.Kind == "" && template.Group == "" && template.ID == "" { + c.templates[""] = &templates[i] + } } } diff --git a/pkg/server/cli/clicontext.go b/pkg/server/cli/clicontext.go index e0feb3f1..24199014 100644 --- a/pkg/server/cli/clicontext.go +++ b/pkg/server/cli/clicontext.go @@ -15,7 +15,6 @@ type Config struct { KubeConfig string HTTPSListenPort int HTTPListenPort int - DashboardURL string Authentication bool WebhookConfig authcli.WebhookConfig @@ -31,8 +30,7 @@ func (c *Config) MustServer(ctx context.Context) *server.Server { func (c *Config) ToServer(ctx context.Context) (*server.Server, error) { var ( - auth steveauth.Middleware - startHooks []server.StartHook + auth steveauth.Middleware ) restConfig, err := kubeconfig.GetNonInteractiveClientConfig(c.KubeConfig).ClientConfig() @@ -48,14 +46,9 @@ func (c *Config) ToServer(ctx context.Context) (*server.Server, error) { } } - return &server.Server{ - RESTConfig: restConfig, + return server.New(ctx, restConfig, &server.Options{ AuthMiddleware: auth, - DashboardURL: func() string { - return c.DashboardURL - }, - StartHooks: startHooks, - }, nil + }) } func Flags(config *Config) []cli.Flag { @@ -75,11 +68,6 @@ func Flags(config *Config) []cli.Flag { Value: 9080, Destination: &config.HTTPListenPort, }, - cli.StringFlag{ - Name: "dashboard-url", - Value: "https://releases.rancher.com/dashboard/latest/index.html", - Destination: &config.DashboardURL, - }, cli.BoolTFlag{ Name: "authentication", Destination: &config.Authentication, diff --git a/pkg/server/config.go b/pkg/server/config.go index cb737fb2..0abda521 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -2,16 +2,8 @@ package server import ( "context" - "net/http" "time" - "github.com/rancher/apiserver/pkg/types" - "github.com/rancher/steve/pkg/accesscontrol" - "github.com/rancher/steve/pkg/auth" - "github.com/rancher/steve/pkg/client" - "github.com/rancher/steve/pkg/clustercache" - "github.com/rancher/steve/pkg/schema" - "github.com/rancher/steve/pkg/server/router" "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io" apiextensionsv1beta1 "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io/v1beta1" "github.com/rancher/wrangler/pkg/generated/controllers/apiregistration.k8s.io" @@ -27,24 +19,6 @@ import ( "k8s.io/client-go/rest" ) -type Server struct { - *Controllers - - RESTConfig *rest.Config - - ClientFactory *client.Factory - BaseSchemas *types.APISchemas - AccessSetLookup accesscontrol.AccessSetLookup - SchemaTemplates []schema.Template - AuthMiddleware auth.Middleware - Next http.Handler - Router router.RouterFunc - ClusterCache clustercache.ClusterCache - PostStartHooks []func() error - StartHooks []StartHook - DashboardURL func() string -} - type Controllers struct { RESTConfig *rest.Config K8s kubernetes.Interface diff --git a/pkg/server/handler/apiserver.go b/pkg/server/handler/apiserver.go index 2b5f07e1..fef712fa 100644 --- a/pkg/server/handler/apiserver.go +++ b/pkg/server/handler/apiserver.go @@ -38,13 +38,12 @@ func New(cfg *rest.Config, sf schema.Factory, authMiddleware auth.Middleware, ne proxy = k8sproxy.ImpersonatingHandler("/", cfg) } - w := authMiddleware.Wrap + w := authMiddleware handlers := router.Handlers{ - Next: next, - K8sResource: w(a.apiHandler(k8sAPI)), - GenericResource: w(a.apiHandler(nil)), - K8sProxy: w(proxy), - APIRoot: w(a.apiHandler(apiRoot)), + Next: next, + K8sResource: w(a.apiHandler(k8sAPI)), + K8sProxy: w(proxy), + APIRoot: w(a.apiHandler(apiRoot)), } if routerFunc == nil { return router.Routes(handlers), nil @@ -89,16 +88,12 @@ type APIFunc func(schema.Factory, *types.APIRequest) func (a *apiServer) apiHandler(apiFunc APIFunc) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - a.api(rw, req, apiFunc) + apiOp, ok := a.common(rw, req) + if ok { + if apiFunc != nil { + apiFunc(a.sf, apiOp) + } + a.server.Handle(apiOp) + } }) } - -func (a *apiServer) api(rw http.ResponseWriter, req *http.Request, apiFunc APIFunc) { - apiOp, ok := a.common(rw, req) - if ok { - if apiFunc != nil { - apiFunc(a.sf, apiOp) - } - a.server.Handle(apiOp) - } -} diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index a9c2880d..8ad7e757 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -10,11 +10,10 @@ import ( type RouterFunc func(h Handlers) http.Handler type Handlers struct { - K8sResource http.Handler - GenericResource http.Handler - APIRoot http.Handler - K8sProxy http.Handler - Next http.Handler + K8sResource http.Handler + APIRoot http.Handler + K8sProxy http.Handler + Next http.Handler } func Routes(h Handlers) http.Handler { diff --git a/pkg/server/server.go b/pkg/server/server.go index 3d745d53..3cff6957 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -8,35 +8,87 @@ import ( "github.com/rancher/apiserver/pkg/types" "github.com/rancher/dynamiclistener/server" "github.com/rancher/steve/pkg/accesscontrol" + "github.com/rancher/steve/pkg/auth" "github.com/rancher/steve/pkg/client" "github.com/rancher/steve/pkg/clustercache" schemacontroller "github.com/rancher/steve/pkg/controllers/schema" - "github.com/rancher/steve/pkg/dashboard" "github.com/rancher/steve/pkg/resources" "github.com/rancher/steve/pkg/resources/common" "github.com/rancher/steve/pkg/resources/schemas" "github.com/rancher/steve/pkg/schema" "github.com/rancher/steve/pkg/server/handler" + "github.com/rancher/steve/pkg/server/router" "github.com/rancher/steve/pkg/summarycache" + "k8s.io/client-go/rest" ) var ErrConfigRequired = errors.New("rest config is required") +type Server struct { + http.Handler + + ClientFactory *client.Factory + ClusterCache clustercache.ClusterCache + SchemaFactory schema.Factory + RESTConfig *rest.Config + BaseSchemas *types.APISchemas + AccessSetLookup accesscontrol.AccessSetLookup + + authMiddleware auth.Middleware + controllers *Controllers + needControllerStart bool + next http.Handler + router router.RouterFunc +} + +type Options struct { + // Controllers If the controllers are passed in the caller must also start the controllers + Controllers *Controllers + ClientFactory *client.Factory + AccessSetLookup accesscontrol.AccessSetLookup + AuthMiddleware auth.Middleware + Next http.Handler + Router router.RouterFunc +} + +func New(ctx context.Context, restConfig *rest.Config, opts *Options) (*Server, error) { + if opts == nil { + opts = &Options{} + } + + server := &Server{ + RESTConfig: restConfig, + ClientFactory: opts.ClientFactory, + AccessSetLookup: opts.AccessSetLookup, + authMiddleware: opts.AuthMiddleware, + controllers: opts.Controllers, + next: opts.Next, + router: opts.Router, + } + + if err := setup(ctx, server); err != nil { + return nil, err + } + + return server, server.start(ctx) +} + func setDefaults(server *Server) error { if server.RESTConfig == nil { return ErrConfigRequired } - if server.Controllers == nil { + if server.controllers == nil { var err error - server.Controllers, err = NewController(server.RESTConfig, nil) + server.controllers, err = NewController(server.RESTConfig, nil) + server.needControllerStart = true if err != nil { return err } } - if server.Next == nil { - server.Next = http.NotFoundHandler() + if server.next == nil { + server.next = http.NotFoundHandler() } if server.BaseSchemas == nil { @@ -46,24 +98,24 @@ func setDefaults(server *Server) error { return nil } -func setup(ctx context.Context, server *Server) (http.Handler, *schema.Collection, error) { +func setup(ctx context.Context, server *Server) error { err := setDefaults(server) if err != nil { - return nil, nil, err + return err } cf := server.ClientFactory if cf == nil { - cf, err = client.NewFactory(server.RESTConfig, server.AuthMiddleware != nil) + cf, err = client.NewFactory(server.RESTConfig, server.authMiddleware != nil) if err != nil { - return nil, nil, err + return err } server.ClientFactory = cf } asl := server.AccessSetLookup if asl == nil { - asl = accesscontrol.NewAccessStore(ctx, true, server.RBAC) + asl = accesscontrol.NewAccessStore(ctx, true, server.controllers.RBAC) } ccache := clustercache.NewClusterCache(ctx, cf.DynamicClient()) @@ -71,7 +123,7 @@ func setup(ctx context.Context, server *Server) (http.Handler, *schema.Collectio server.BaseSchemas, err = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, cf) if err != nil { - return nil, nil, err + return err } sf := schema.NewCollection(ctx, server.BaseSchemas, asl) @@ -80,88 +132,53 @@ func setup(ctx context.Context, server *Server) (http.Handler, *schema.Collectio ccache.OnRemove(ctx, summaryCache.OnRemove) ccache.OnChange(ctx, summaryCache.OnChange) - server.SchemaTemplates = append(server.SchemaTemplates, resources.DefaultSchemaTemplates(cf, summaryCache, asl, server.K8s.Discovery())...) + for _, template := range resources.DefaultSchemaTemplates(cf, summaryCache, asl, server.controllers.K8s.Discovery()) { + sf.AddTemplate(template) + } cols, err := common.NewDynamicColumns(server.RESTConfig) if err != nil { - return nil, nil, err + return err } schemas.SetupWatcher(ctx, server.BaseSchemas, asl, sf) - sync := schemacontroller.Register(ctx, + schemacontroller.Register(ctx, cols, - server.K8s.Discovery(), - server.CRD.CustomResourceDefinition(), - server.API.APIService(), - server.K8s.AuthorizationV1().SelfSubjectAccessReviews(), + server.controllers.K8s.Discovery(), + server.controllers.CRD.CustomResourceDefinition(), + server.controllers.API.APIService(), + server.controllers.K8s.AuthorizationV1().SelfSubjectAccessReviews(), ccache, sf) - handler, err := handler.New(server.RESTConfig, sf, server.AuthMiddleware, server.Next, server.Router) - if err != nil { - return nil, nil, err - } - - server.PostStartHooks = append(server.PostStartHooks, func() error { - return sync() - }) - - if server.DashboardURL != nil && server.DashboardURL() != "" { - handler = dashboard.Route(handler, server.DashboardURL) - } - - return handler, sf, nil -} - -func (c *Server) Handler(ctx context.Context) (http.Handler, error) { - handler, sf, err := setup(ctx, c) - if err != nil { - return nil, err - } - - c.Next = handler - - for _, hook := range c.StartHooks { - if err := hook(ctx, c); err != nil { - return nil, err - } - } - - for i := range c.SchemaTemplates { - sf.AddTemplate(&c.SchemaTemplates[i]) - } - - if err := c.Controllers.Start(ctx); err != nil { - return nil, err - } - - for _, hook := range c.PostStartHooks { - if err := hook(); err != nil { - return nil, err - } - } - - return c.Next, nil -} - -func (c *Server) ListenAndServe(ctx context.Context, httpsPort, httpPort int, opts *server.ListenOpts) error { - handler, err := c.Handler(ctx) + handler, err := handler.New(server.RESTConfig, sf, server.authMiddleware, server.next, server.router) if err != nil { return err } + server.Handler = handler + server.SchemaFactory = sf + return nil +} + +func (c *Server) start(ctx context.Context) error { + if c.needControllerStart { + if err := c.controllers.Start(ctx); err != nil { + return err + } + } + return nil +} + +func (c *Server) ListenAndServe(ctx context.Context, httpsPort, httpPort int, opts *server.ListenOpts) error { if opts == nil { opts = &server.ListenOpts{} } if opts.Storage == nil && opts.Secrets == nil { - opts.Secrets = c.Core.Secret() + opts.Secrets = c.controllers.Core.Secret() } - if err := server.ListenAndServe(ctx, httpsPort, httpPort, handler, opts); err != nil { - return err - } - - if err := c.Controllers.Start(ctx); err != nil { + if err := server.ListenAndServe(ctx, httpsPort, httpPort, c, opts); err != nil { return err } diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index e84b5e0a..e6087212 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -343,7 +343,7 @@ func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w t go func() { defer close(result) for item := range c { - if item.Error != nil && names.Has(item.Object.Name()) { + if item.Error == nil && names.Has(item.Object.Name()) { result <- item } } diff --git a/test.yaml b/test.yaml new file mode 100644 index 00000000..9d35fc74 --- /dev/null +++ b/test.yaml @@ -0,0 +1,9 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + generateName: test- + ownerReferences: + - kind: bad + apiVersion: bad + name: bad + uid: bad2 diff --git a/test2.yaml b/test2.yaml new file mode 100644 index 00000000..79ec539c --- /dev/null +++ b/test2.yaml @@ -0,0 +1,9 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + generateName: child- + ownerReferences: + - kind: ConfigMap + apiVersion: v1 + name: bad + uid: bad diff --git a/user.yaml b/user.yaml new file mode 100644 index 00000000..93dfbce5 --- /dev/null +++ b/user.yaml @@ -0,0 +1,20 @@ +apiVersion: management.cattle.io/v3 +displayName: Default Admin +kind: User +metadata: + name: admin +password: $2a$10$LWysOkIa80mPXJ9W9dyuteKEP2LIswCURen2fNNuKhnjQ1RLYV00K +username: admin + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: imclusteradmin +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: User + name: admin