2019-08-13 23:36:03 +00:00
|
|
|
package schema
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
2020-06-12 04:50:59 +00:00
|
|
|
"github.com/rancher/apiserver/pkg/types"
|
2019-09-11 21:05:00 +00:00
|
|
|
"github.com/rancher/steve/pkg/attributes"
|
2020-06-12 04:50:59 +00:00
|
|
|
"github.com/rancher/steve/pkg/resources/common"
|
2020-01-31 05:37:59 +00:00
|
|
|
schema2 "github.com/rancher/steve/pkg/schema"
|
|
|
|
"github.com/rancher/steve/pkg/schema/converter"
|
2024-06-04 18:52:48 +00:00
|
|
|
apiextcontrollerv1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiextensions.k8s.io/v1"
|
|
|
|
v1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiregistration.k8s.io/v1"
|
2019-08-13 23:36:03 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2020-02-08 20:04:20 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"golang.org/x/sync/semaphore"
|
2019-09-11 17:32:01 +00:00
|
|
|
authorizationv1 "k8s.io/api/authorization/v1"
|
2021-04-21 16:49:03 +00:00
|
|
|
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
2020-03-26 20:58:39 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2019-08-13 23:36:03 +00:00
|
|
|
"k8s.io/client-go/discovery"
|
2019-09-11 17:32:01 +00:00
|
|
|
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
|
2019-08-13 23:36:03 +00:00
|
|
|
apiv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
|
|
|
)
|
|
|
|
|
2020-02-08 20:04:20 +00:00
|
|
|
var (
|
2020-03-12 21:01:40 +00:00
|
|
|
listPool = semaphore.NewWeighted(10)
|
|
|
|
typeNameChanges = map[string]string{
|
|
|
|
"extensions.v1beta1.ingress": "networking.k8s.io.v1beta1.ingress",
|
|
|
|
}
|
2020-02-08 20:04:20 +00:00
|
|
|
)
|
|
|
|
|
2024-06-05 14:17:12 +00:00
|
|
|
type SchemasHandlerFunc func(schemas *schema2.Collection) error
|
|
|
|
|
|
|
|
func (s SchemasHandlerFunc) OnSchemas(schemas *schema2.Collection) error {
|
|
|
|
return s(schemas)
|
2019-09-09 21:28:55 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 23:36:03 +00:00
|
|
|
type handler struct {
|
|
|
|
sync.Mutex
|
|
|
|
|
2020-02-01 04:29:43 +00:00
|
|
|
ctx context.Context
|
2019-08-13 23:36:03 +00:00
|
|
|
toSync int32
|
|
|
|
schemas *schema2.Collection
|
|
|
|
client discovery.DiscoveryInterface
|
2020-02-08 20:04:20 +00:00
|
|
|
cols *common.DynamicColumns
|
2021-04-21 16:49:03 +00:00
|
|
|
crd apiextcontrollerv1.CustomResourceDefinitionClient
|
2019-09-11 17:32:01 +00:00
|
|
|
ssar authorizationv1client.SelfSubjectAccessReviewInterface
|
2024-06-05 14:17:12 +00:00
|
|
|
handler SchemasHandlerFunc
|
2019-08-13 23:36:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func Register(ctx context.Context,
|
2020-02-08 20:04:20 +00:00
|
|
|
cols *common.DynamicColumns,
|
2019-08-13 23:36:03 +00:00
|
|
|
discovery discovery.DiscoveryInterface,
|
2021-04-21 16:49:03 +00:00
|
|
|
crd apiextcontrollerv1.CustomResourceDefinitionController,
|
2019-08-13 23:36:03 +00:00
|
|
|
apiService v1.APIServiceController,
|
2019-09-11 17:32:01 +00:00
|
|
|
ssar authorizationv1client.SelfSubjectAccessReviewInterface,
|
2024-06-05 14:17:12 +00:00
|
|
|
schemasHandler SchemasHandlerFunc,
|
2020-07-24 08:42:00 +00:00
|
|
|
schemas *schema2.Collection) {
|
2019-08-13 23:36:03 +00:00
|
|
|
|
|
|
|
h := &handler{
|
2020-02-01 04:29:43 +00:00
|
|
|
ctx: ctx,
|
2020-02-08 20:04:20 +00:00
|
|
|
cols: cols,
|
2019-08-13 23:36:03 +00:00
|
|
|
client: discovery,
|
|
|
|
schemas: schemas,
|
2019-09-09 21:28:55 +00:00
|
|
|
handler: schemasHandler,
|
2019-08-14 18:08:34 +00:00
|
|
|
crd: crd,
|
2019-09-11 17:32:01 +00:00
|
|
|
ssar: ssar,
|
2019-08-13 23:36:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
apiService.OnChange(ctx, "schema", h.OnChangeAPIService)
|
|
|
|
crd.OnChange(ctx, "schema", h.OnChangeCRD)
|
|
|
|
}
|
|
|
|
|
2021-04-21 16:49:03 +00:00
|
|
|
func (h *handler) OnChangeCRD(key string, crd *apiextv1.CustomResourceDefinition) (*apiextv1.CustomResourceDefinition, error) {
|
2019-09-09 21:28:55 +00:00
|
|
|
h.queueRefresh()
|
|
|
|
return crd, nil
|
2019-08-13 23:36:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *handler) OnChangeAPIService(key string, api *apiv1.APIService) (*apiv1.APIService, error) {
|
2019-09-09 21:28:55 +00:00
|
|
|
h.queueRefresh()
|
|
|
|
return api, nil
|
2019-08-13 23:36:03 +00:00
|
|
|
}
|
|
|
|
|
2019-09-09 21:28:55 +00:00
|
|
|
func (h *handler) queueRefresh() {
|
2019-08-13 23:36:03 +00:00
|
|
|
atomic.StoreInt32(&h.toSync, 1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
2020-03-26 20:58:39 +00:00
|
|
|
if err := h.refreshAll(h.ctx); err != nil {
|
2019-08-13 23:36:03 +00:00
|
|
|
logrus.Errorf("failed to sync schemas: %v", err)
|
|
|
|
atomic.StoreInt32(&h.toSync, 1)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-03-18 19:17:40 +00:00
|
|
|
func isListOrGetable(schema *types.APISchema) bool {
|
|
|
|
for _, verb := range attributes.Verbs(schema) {
|
|
|
|
switch verb {
|
|
|
|
case "list":
|
|
|
|
return true
|
|
|
|
case "get":
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-31 05:37:59 +00:00
|
|
|
func isListWatchable(schema *types.APISchema) bool {
|
2019-09-11 17:32:01 +00:00
|
|
|
var (
|
|
|
|
canList bool
|
|
|
|
canWatch bool
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, verb := range attributes.Verbs(schema) {
|
|
|
|
switch verb {
|
|
|
|
case "list":
|
|
|
|
canList = true
|
|
|
|
case "watch":
|
|
|
|
canWatch = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return canList && canWatch
|
|
|
|
}
|
|
|
|
|
2020-02-08 20:04:20 +00:00
|
|
|
func (h *handler) getColumns(ctx context.Context, schemas map[string]*types.APISchema) error {
|
|
|
|
eg := errgroup.Group{}
|
|
|
|
|
|
|
|
for _, schema := range schemas {
|
2020-03-18 19:17:40 +00:00
|
|
|
if !isListOrGetable(schema) {
|
2020-02-08 20:04:20 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := listPool.Acquire(ctx, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s := schema
|
|
|
|
eg.Go(func() error {
|
|
|
|
defer listPool.Release(1)
|
2020-03-26 20:58:39 +00:00
|
|
|
return h.cols.SetColumns(ctx, s)
|
2020-02-08 20:04:20 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return eg.Wait()
|
|
|
|
}
|
|
|
|
|
2020-03-26 20:58:39 +00:00
|
|
|
func (h *handler) refreshAll(ctx context.Context) error {
|
2019-08-13 23:36:03 +00:00
|
|
|
h.Lock()
|
|
|
|
defer h.Unlock()
|
|
|
|
|
|
|
|
if !h.needToSync() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-08-14 18:08:34 +00:00
|
|
|
schemas, err := converter.ToSchemas(h.crd, h.client)
|
2019-08-13 23:36:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-31 05:37:59 +00:00
|
|
|
filteredSchemas := map[string]*types.APISchema{}
|
2020-03-11 06:12:04 +00:00
|
|
|
for _, schema := range schemas {
|
2019-09-11 17:32:01 +00:00
|
|
|
if isListWatchable(schema) {
|
2020-03-12 21:01:40 +00:00
|
|
|
if preferredTypeExists(schema, schemas) {
|
2020-03-11 06:12:04 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-03-26 20:58:39 +00:00
|
|
|
if ok, err := h.allowed(ctx, schema); err != nil {
|
2019-09-11 17:32:01 +00:00
|
|
|
return err
|
|
|
|
} else if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2020-03-11 06:12:04 +00:00
|
|
|
|
|
|
|
gvk := attributes.GVK(schema)
|
|
|
|
if gvk.Kind != "" {
|
|
|
|
gvr := attributes.GVR(schema)
|
|
|
|
schema.ID = converter.GVKToSchemaID(gvk)
|
|
|
|
schema.PluralName = converter.GVRToPluralName(gvr)
|
|
|
|
}
|
|
|
|
filteredSchemas[schema.ID] = schema
|
2019-09-11 17:32:01 +00:00
|
|
|
}
|
|
|
|
|
2020-02-08 20:04:20 +00:00
|
|
|
if err := h.getColumns(h.ctx, filteredSchemas); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-09-11 17:32:01 +00:00
|
|
|
h.schemas.Reset(filteredSchemas)
|
2019-09-09 21:28:55 +00:00
|
|
|
if h.handler != nil {
|
|
|
|
return h.handler.OnSchemas(h.schemas)
|
|
|
|
}
|
2019-08-13 23:36:03 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-12 21:01:40 +00:00
|
|
|
func preferredTypeExists(schema *types.APISchema, schemas map[string]*types.APISchema) bool {
|
|
|
|
if replacement, ok := typeNameChanges[schema.ID]; ok && schemas[replacement] != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
pg := attributes.PreferredGroup(schema)
|
|
|
|
pv := attributes.PreferredVersion(schema)
|
|
|
|
if pg == "" && pv == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
gvk := attributes.GVK(schema)
|
|
|
|
if pg != "" {
|
|
|
|
gvk.Group = pg
|
|
|
|
}
|
|
|
|
if pv != "" {
|
|
|
|
gvk.Version = pv
|
|
|
|
}
|
|
|
|
|
|
|
|
_, ok := schemas[converter.GVKToVersionedSchemaID(gvk)]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2020-03-26 20:58:39 +00:00
|
|
|
func (h *handler) allowed(ctx context.Context, schema *types.APISchema) (bool, error) {
|
2019-09-11 17:32:01 +00:00
|
|
|
gvr := attributes.GVR(schema)
|
2020-03-26 20:58:39 +00:00
|
|
|
ssar, err := h.ssar.Create(ctx, &authorizationv1.SelfSubjectAccessReview{
|
2019-09-11 17:32:01 +00:00
|
|
|
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
|
|
|
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
|
|
|
Verb: "list",
|
|
|
|
Group: gvr.Group,
|
|
|
|
Version: gvr.Version,
|
|
|
|
Resource: gvr.Resource,
|
|
|
|
},
|
|
|
|
},
|
2020-03-26 20:58:39 +00:00
|
|
|
}, metav1.CreateOptions{})
|
2019-09-11 17:32:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return ssar.Status.Allowed && !ssar.Status.Denied, nil
|
|
|
|
}
|
|
|
|
|
2019-08-13 23:36:03 +00:00
|
|
|
func (h *handler) needToSync() bool {
|
|
|
|
old := atomic.SwapInt32(&h.toSync, 0)
|
|
|
|
return old == 1
|
|
|
|
}
|