Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions config/crds/apis.kcp.io_apibindings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,43 @@ spec:
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
references:
description: |-
references is scoping the access to claimed objects down to those that
are explicitly named in fields in the bound resources.
items:
description: |-
PermissionClaimReference describes an object by specifying in what bound resource
it is named.
properties:
group:
description: group is the API group of the bound resource
in which the reference must occur.
type: string
jsonPath:
description: jsonPath uses JSONPath expressions to
select the referent.
properties:
name:
description: name is the JSONPath to select the
referent object name.
type: string
namespace:
description: namespace is the JSONPath to select
the referent object namespace.
type: string
required:
- name
type: object
resource:
description: resource is the bound resource in which
the reference must occur.
type: string
required:
- group
- resource
type: object
type: array
type: object
x-kubernetes-map-type: atomic
x-kubernetes-validations:
Expand Down Expand Up @@ -702,6 +739,43 @@ spec:
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
references:
description: |-
references is scoping the access to claimed objects down to those that
are explicitly named in fields in the bound resources.
items:
description: |-
PermissionClaimReference describes an object by specifying in what bound resource
it is named.
properties:
group:
description: group is the API group of the bound resource
in which the reference must occur.
type: string
jsonPath:
description: jsonPath uses JSONPath expressions to
select the referent.
properties:
name:
description: name is the JSONPath to select the
referent object name.
type: string
namespace:
description: namespace is the JSONPath to select
the referent object namespace.
type: string
required:
- name
type: object
resource:
description: resource is the bound resource in which
the reference must occur.
type: string
required:
- group
- resource
type: object
type: array
type: object
x-kubernetes-map-type: atomic
x-kubernetes-validations:
Expand Down
17 changes: 17 additions & 0 deletions pkg/admission/initializers/initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
kcpkubernetesinformers "github.com/kcp-dev/client-go/informers"
kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes"

"github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper"
kcpclientset "github.com/kcp-dev/kcp/sdk/client/clientset/versioned/cluster"
kcpinformers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions"
)
Expand Down Expand Up @@ -182,3 +183,19 @@ func (i *dynamicClusterClientInitializer) Initialize(plugin admission.Interface)
wants.SetDynamicClusterClient(i.dynamicClusterClient)
}
}

type dynamicRESTMapperInitializer struct {
mapper *dynamicrestmapper.DynamicRESTMapper
}

func NewDynamicRESTMapperInitializer(mapper *dynamicrestmapper.DynamicRESTMapper) *dynamicRESTMapperInitializer {
return &dynamicRESTMapperInitializer{
mapper: mapper,
}
}

func (i *dynamicRESTMapperInitializer) Initialize(plugin admission.Interface) {
if wants, ok := plugin.(WantsDynamicRESTMapper); ok {
wants.SetDynamicRESTMapper(i.mapper)
}
}
6 changes: 6 additions & 0 deletions pkg/admission/initializers/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
kcpkubernetesinformers "github.com/kcp-dev/client-go/informers"
kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes"

"github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper"
kcpclientset "github.com/kcp-dev/kcp/sdk/client/clientset/versioned/cluster"
kcpinformers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions"
)
Expand Down Expand Up @@ -67,3 +68,8 @@ type WantsServerShutdownChannel interface {
type WantsDynamicClusterClient interface {
SetDynamicClusterClient(clusterInterface kcpdynamic.ClusterInterface)
}

// WantsDynamicRESTMapper is an interface that should be implemented by admission plugins that need a dynamic REST mapper.
type WantsDynamicRESTMapper interface {
SetDynamicRESTMapper(mapper *dynamicrestmapper.DynamicRESTMapper)
}
95 changes: 85 additions & 10 deletions pkg/admission/permissionclaims/mutating_permission_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ import (
"io"
"strings"

kcpdynamic "github.com/kcp-dev/client-go/dynamic"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/tools/cache"

"github.com/kcp-dev/kcp/pkg/permissionclaim"
"github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper"
apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
kcpinformers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions"
)
Expand All @@ -49,6 +53,11 @@ type mutatingPermissionClaims struct {

apiBindingsHasSynced cache.InformerSynced

// these are kept temporarily until all of them are set, then the claimLabeler is created
local, global kcpinformers.SharedInformerFactory
dynRESTMapper *dynamicrestmapper.DynamicRESTMapper
dynClusterClient kcpdynamic.ClusterInterface

permissionClaimLabeler *permissionclaim.Labeler
}

Expand All @@ -57,7 +66,7 @@ var _ admission.ValidationInterface = &mutatingPermissionClaims{}
var _ admission.InitializationValidator = &mutatingPermissionClaims{}

// NewMutatingPermissionClaims creates a mutating admission plugin that is responsible for labeling objects
// according to permission claims. or every creation and update request, we will determine the bindings
// according to permission claims. For every creation and update request, we will determine the bindings
// in the workspace and if the object is claimed by an accepted permission claim we will add the label,
// and remove those that are not backed by a permission claim anymore.
func NewMutatingPermissionClaims() admission.MutationInterface {
Expand All @@ -84,12 +93,22 @@ func (m *mutatingPermissionClaims) Admit(ctx context.Context, a admission.Attrib
return fmt.Errorf("got type %T, expected metav1.Object", a.GetObject())
}

uObject, err := toUnstructured(u)
if err != nil {
return err
}

clusterName, err := genericapirequest.ClusterNameFrom(ctx)
if err != nil {
return err
}

expectedLabels, err := m.permissionClaimLabeler.LabelsFor(ctx, clusterName, a.GetResource().GroupResource(), a.GetName(), u.GetLabels())
labeler := m.getLabeler()
if labeler == nil {
return errors.New("no DDSIF provided yet")
}

expectedLabels, err := labeler.LabelsFor(ctx, clusterName, a.GetResource().GroupResource(), uObject)
if err != nil {
return err
}
Expand Down Expand Up @@ -124,12 +143,22 @@ func (m *mutatingPermissionClaims) Validate(ctx context.Context, a admission.Att
return fmt.Errorf("expected type %T, expected metav1.Object", a.GetObject())
}

uObject, err := toUnstructured(u)
if err != nil {
return err
}

clusterName, err := genericapirequest.ClusterNameFrom(ctx)
if err != nil {
return err
}

expectedLabels, err := m.permissionClaimLabeler.LabelsFor(ctx, clusterName, a.GetResource().GroupResource(), a.GetName(), u.GetLabels())
labeler := m.getLabeler()
if labeler == nil {
return errors.New("no DDSIF provided yet")
}

expectedLabels, err := labeler.LabelsFor(ctx, clusterName, a.GetResource().GroupResource(), uObject)
if err != nil {
return err
}
Expand Down Expand Up @@ -164,20 +193,66 @@ func (m *mutatingPermissionClaims) Validate(ctx context.Context, a admission.Att
// SetKcpInformers implements the WantsExternalKcpInformerFactory interface.
func (m *mutatingPermissionClaims) SetKcpInformers(local, global kcpinformers.SharedInformerFactory) {
m.apiBindingsHasSynced = local.Apis().V1alpha2().APIBindings().Informer().HasSynced
m.local = local
m.global = global
}

m.permissionClaimLabeler = permissionclaim.NewLabeler(
local.Apis().V1alpha2().APIBindings(),
local.Apis().V1alpha2().APIExports(),
global.Apis().V1alpha2().APIExports(),
)
// SetDynamicClusterClient implements the WantsDynamicClusterClient interface.
func (m *mutatingPermissionClaims) SetDynamicClusterClient(clusterInterface kcpdynamic.ClusterInterface) {
m.dynClusterClient = clusterInterface
}

// SetDynamicRESTMapper implements the WantsDynamicRESTMapper interface.
func (m *mutatingPermissionClaims) SetDynamicRESTMapper(mapper *dynamicrestmapper.DynamicRESTMapper) {
m.dynRESTMapper = mapper
}

func (m *mutatingPermissionClaims) ValidateInitialization() error {
if m.apiBindingsHasSynced == nil {
return errors.New("missing apiBindingsHasSynced")
}
if m.permissionClaimLabeler == nil {
return errors.New("missing permissionClaimLabeler")
if m.local == nil {
return errors.New("missing local informer factory")
}
if m.global == nil {
return errors.New("missing global informer factory")
}
if m.dynClusterClient == nil {
return errors.New("missing dynamic cluster client")
}
if m.dynRESTMapper == nil {
return errors.New("missing dynamic REST mapper")
}

return nil
}

func (m *mutatingPermissionClaims) getLabeler() *permissionclaim.Labeler {
if m.permissionClaimLabeler != nil {
return m.permissionClaimLabeler
}

// wait until all initializers are done
if m.ValidateInitialization() != nil {
return nil
}

m.permissionClaimLabeler = permissionclaim.NewLabeler(
m.local.Apis().V1alpha2().APIBindings(),
m.local.Apis().V1alpha2().APIExports(),
m.global.Apis().V1alpha2().APIExports(),
m.dynRESTMapper,
m.dynClusterClient,
)

return m.permissionClaimLabeler
}

func toUnstructured(obj metav1.Object) (*unstructured.Unstructured, error) {
raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}

return &unstructured.Unstructured{Object: raw}, nil
}
Loading