This commit is contained in:
Darren Shepherd 2020-07-24 01:42:00 -07:00
parent f403507ea9
commit a3fbe499d1
13 changed files with 195 additions and 264 deletions

View File

@ -16,6 +16,11 @@ import (
"k8s.io/client-go/transport" "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 { type Authenticator interface {
Authenticate(req *http.Request) (user.Info, bool, error) 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) 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 { func (m Middleware) Chain(middleware Middleware) Middleware {
if m == nil { return func(next http.Handler) http.Handler {
return 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) { 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 { func ToMiddleware(auth Authenticator) Middleware {
return func(rw http.ResponseWriter, req *http.Request, next http.Handler) { return func(next http.Handler) http.Handler {
info, ok, err := auth.Authenticate(req) return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if err != nil { info, ok, err := auth.Authenticate(req)
rw.WriteHeader(http.StatusUnauthorized) if err != nil {
rw.Write([]byte(err.Error())) info = &user.DefaultInfo{
return 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 { ctx := request.WithUser(req.Context(), info)
rw.WriteHeader(http.StatusUnauthorized) req = req.WithContext(ctx)
return next.ServeHTTP(rw, req)
} })
ctx := request.WithUser(req.Context(), info)
req = req.WithContext(ctx)
next.ServeHTTP(rw, req)
} }
} }

View File

@ -55,7 +55,7 @@ func Register(ctx context.Context,
apiService v1.APIServiceController, apiService v1.APIServiceController,
ssar authorizationv1client.SelfSubjectAccessReviewInterface, ssar authorizationv1client.SelfSubjectAccessReviewInterface,
schemasHandler SchemasHandler, schemasHandler SchemasHandler,
schemas *schema2.Collection) (init func() error) { schemas *schema2.Collection) {
h := &handler{ h := &handler{
ctx: ctx, ctx: ctx,
@ -69,11 +69,6 @@ func Register(ctx context.Context,
apiService.OnChange(ctx, "schema", h.OnChangeAPIService) apiService.OnChange(ctx, "schema", h.OnChangeAPIService)
crd.OnChange(ctx, "schema", h.OnChangeCRD) 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) { func (h *handler) OnChangeCRD(key string, crd *v1beta1.CustomResourceDefinition) (*v1beta1.CustomResourceDefinition, error) {

View File

@ -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
}

View File

@ -23,6 +23,7 @@ type Factory interface {
ByGVR(gvr schema.GroupVersionResource) string ByGVR(gvr schema.GroupVersionResource) string
ByGVK(gvr schema.GroupVersionKind) string ByGVK(gvr schema.GroupVersionKind) string
OnChange(ctx context.Context, cb func()) OnChange(ctx context.Context, cb func())
AddTemplate(template ...Template)
} }
type Collection struct { type Collection struct {
@ -210,17 +211,19 @@ func (c *Collection) ByGVK(gvk schema.GroupVersionKind) string {
return c.byGVK[gvk] return c.byGVK[gvk]
} }
func (c *Collection) AddTemplate(template *Template) { func (c *Collection) AddTemplate(templates ...Template) {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
if template.Kind != "" { for i, template := range templates {
c.templates[template.Group+"/"+template.Kind] = template if template.Kind != "" {
} c.templates[template.Group+"/"+template.Kind] = &templates[i]
if template.ID != "" { }
c.templates[template.ID] = template if template.ID != "" {
} c.templates[template.ID] = &templates[i]
if template.Kind == "" && template.Group == "" && template.ID == "" { }
c.templates[""] = template if template.Kind == "" && template.Group == "" && template.ID == "" {
c.templates[""] = &templates[i]
}
} }
} }

View File

@ -15,7 +15,6 @@ type Config struct {
KubeConfig string KubeConfig string
HTTPSListenPort int HTTPSListenPort int
HTTPListenPort int HTTPListenPort int
DashboardURL string
Authentication bool Authentication bool
WebhookConfig authcli.WebhookConfig 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) { func (c *Config) ToServer(ctx context.Context) (*server.Server, error) {
var ( var (
auth steveauth.Middleware auth steveauth.Middleware
startHooks []server.StartHook
) )
restConfig, err := kubeconfig.GetNonInteractiveClientConfig(c.KubeConfig).ClientConfig() restConfig, err := kubeconfig.GetNonInteractiveClientConfig(c.KubeConfig).ClientConfig()
@ -48,14 +46,9 @@ func (c *Config) ToServer(ctx context.Context) (*server.Server, error) {
} }
} }
return &server.Server{ return server.New(ctx, restConfig, &server.Options{
RESTConfig: restConfig,
AuthMiddleware: auth, AuthMiddleware: auth,
DashboardURL: func() string { })
return c.DashboardURL
},
StartHooks: startHooks,
}, nil
} }
func Flags(config *Config) []cli.Flag { func Flags(config *Config) []cli.Flag {
@ -75,11 +68,6 @@ func Flags(config *Config) []cli.Flag {
Value: 9080, Value: 9080,
Destination: &config.HTTPListenPort, Destination: &config.HTTPListenPort,
}, },
cli.StringFlag{
Name: "dashboard-url",
Value: "https://releases.rancher.com/dashboard/latest/index.html",
Destination: &config.DashboardURL,
},
cli.BoolTFlag{ cli.BoolTFlag{
Name: "authentication", Name: "authentication",
Destination: &config.Authentication, Destination: &config.Authentication,

View File

@ -2,16 +2,8 @@ package server
import ( import (
"context" "context"
"net/http"
"time" "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" "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io"
apiextensionsv1beta1 "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io/v1beta1" apiextensionsv1beta1 "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io/v1beta1"
"github.com/rancher/wrangler/pkg/generated/controllers/apiregistration.k8s.io" "github.com/rancher/wrangler/pkg/generated/controllers/apiregistration.k8s.io"
@ -27,24 +19,6 @@ import (
"k8s.io/client-go/rest" "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 { type Controllers struct {
RESTConfig *rest.Config RESTConfig *rest.Config
K8s kubernetes.Interface K8s kubernetes.Interface

View File

@ -38,13 +38,12 @@ func New(cfg *rest.Config, sf schema.Factory, authMiddleware auth.Middleware, ne
proxy = k8sproxy.ImpersonatingHandler("/", cfg) proxy = k8sproxy.ImpersonatingHandler("/", cfg)
} }
w := authMiddleware.Wrap w := authMiddleware
handlers := router.Handlers{ handlers := router.Handlers{
Next: next, Next: next,
K8sResource: w(a.apiHandler(k8sAPI)), K8sResource: w(a.apiHandler(k8sAPI)),
GenericResource: w(a.apiHandler(nil)), K8sProxy: w(proxy),
K8sProxy: w(proxy), APIRoot: w(a.apiHandler(apiRoot)),
APIRoot: w(a.apiHandler(apiRoot)),
} }
if routerFunc == nil { if routerFunc == nil {
return router.Routes(handlers), 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 { func (a *apiServer) apiHandler(apiFunc APIFunc) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 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)
}
}

View File

@ -10,11 +10,10 @@ import (
type RouterFunc func(h Handlers) http.Handler type RouterFunc func(h Handlers) http.Handler
type Handlers struct { type Handlers struct {
K8sResource http.Handler K8sResource http.Handler
GenericResource http.Handler APIRoot http.Handler
APIRoot http.Handler K8sProxy http.Handler
K8sProxy http.Handler Next http.Handler
Next http.Handler
} }
func Routes(h Handlers) http.Handler { func Routes(h Handlers) http.Handler {

View File

@ -8,35 +8,87 @@ import (
"github.com/rancher/apiserver/pkg/types" "github.com/rancher/apiserver/pkg/types"
"github.com/rancher/dynamiclistener/server" "github.com/rancher/dynamiclistener/server"
"github.com/rancher/steve/pkg/accesscontrol" "github.com/rancher/steve/pkg/accesscontrol"
"github.com/rancher/steve/pkg/auth"
"github.com/rancher/steve/pkg/client" "github.com/rancher/steve/pkg/client"
"github.com/rancher/steve/pkg/clustercache" "github.com/rancher/steve/pkg/clustercache"
schemacontroller "github.com/rancher/steve/pkg/controllers/schema" 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"
"github.com/rancher/steve/pkg/resources/common" "github.com/rancher/steve/pkg/resources/common"
"github.com/rancher/steve/pkg/resources/schemas" "github.com/rancher/steve/pkg/resources/schemas"
"github.com/rancher/steve/pkg/schema" "github.com/rancher/steve/pkg/schema"
"github.com/rancher/steve/pkg/server/handler" "github.com/rancher/steve/pkg/server/handler"
"github.com/rancher/steve/pkg/server/router"
"github.com/rancher/steve/pkg/summarycache" "github.com/rancher/steve/pkg/summarycache"
"k8s.io/client-go/rest"
) )
var ErrConfigRequired = errors.New("rest config is required") 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 { func setDefaults(server *Server) error {
if server.RESTConfig == nil { if server.RESTConfig == nil {
return ErrConfigRequired return ErrConfigRequired
} }
if server.Controllers == nil { if server.controllers == nil {
var err error var err error
server.Controllers, err = NewController(server.RESTConfig, nil) server.controllers, err = NewController(server.RESTConfig, nil)
server.needControllerStart = true
if err != nil { if err != nil {
return err return err
} }
} }
if server.Next == nil { if server.next == nil {
server.Next = http.NotFoundHandler() server.next = http.NotFoundHandler()
} }
if server.BaseSchemas == nil { if server.BaseSchemas == nil {
@ -46,24 +98,24 @@ func setDefaults(server *Server) error {
return nil 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) err := setDefaults(server)
if err != nil { if err != nil {
return nil, nil, err return err
} }
cf := server.ClientFactory cf := server.ClientFactory
if cf == nil { if cf == nil {
cf, err = client.NewFactory(server.RESTConfig, server.AuthMiddleware != nil) cf, err = client.NewFactory(server.RESTConfig, server.authMiddleware != nil)
if err != nil { if err != nil {
return nil, nil, err return err
} }
server.ClientFactory = cf server.ClientFactory = cf
} }
asl := server.AccessSetLookup asl := server.AccessSetLookup
if asl == nil { if asl == nil {
asl = accesscontrol.NewAccessStore(ctx, true, server.RBAC) asl = accesscontrol.NewAccessStore(ctx, true, server.controllers.RBAC)
} }
ccache := clustercache.NewClusterCache(ctx, cf.DynamicClient()) 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) server.BaseSchemas, err = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, cf)
if err != nil { if err != nil {
return nil, nil, err return err
} }
sf := schema.NewCollection(ctx, server.BaseSchemas, asl) 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.OnRemove(ctx, summaryCache.OnRemove)
ccache.OnChange(ctx, summaryCache.OnChange) 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) cols, err := common.NewDynamicColumns(server.RESTConfig)
if err != nil { if err != nil {
return nil, nil, err return err
} }
schemas.SetupWatcher(ctx, server.BaseSchemas, asl, sf) schemas.SetupWatcher(ctx, server.BaseSchemas, asl, sf)
sync := schemacontroller.Register(ctx, schemacontroller.Register(ctx,
cols, cols,
server.K8s.Discovery(), server.controllers.K8s.Discovery(),
server.CRD.CustomResourceDefinition(), server.controllers.CRD.CustomResourceDefinition(),
server.API.APIService(), server.controllers.API.APIService(),
server.K8s.AuthorizationV1().SelfSubjectAccessReviews(), server.controllers.K8s.AuthorizationV1().SelfSubjectAccessReviews(),
ccache, ccache,
sf) sf)
handler, err := handler.New(server.RESTConfig, sf, server.AuthMiddleware, server.Next, server.Router) 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)
if err != nil { if err != nil {
return err 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 { if opts == nil {
opts = &server.ListenOpts{} opts = &server.ListenOpts{}
} }
if opts.Storage == nil && opts.Secrets == nil { 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 { if err := server.ListenAndServe(ctx, httpsPort, httpPort, c, opts); err != nil {
return err
}
if err := c.Controllers.Start(ctx); err != nil {
return err return err
} }

View File

@ -343,7 +343,7 @@ func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w t
go func() { go func() {
defer close(result) defer close(result)
for item := range c { for item := range c {
if item.Error != nil && names.Has(item.Object.Name()) { if item.Error == nil && names.Has(item.Object.Name()) {
result <- item result <- item
} }
} }

9
test.yaml Normal file
View File

@ -0,0 +1,9 @@
kind: ConfigMap
apiVersion: v1
metadata:
generateName: test-
ownerReferences:
- kind: bad
apiVersion: bad
name: bad
uid: bad2

9
test2.yaml Normal file
View File

@ -0,0 +1,9 @@
kind: ConfigMap
apiVersion: v1
metadata:
generateName: child-
ownerReferences:
- kind: ConfigMap
apiVersion: v1
name: bad
uid: bad

20
user.yaml Normal file
View File

@ -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