diff --git a/config/crds/apis.kcp.io_apibindings.yaml b/config/crds/apis.kcp.io_apibindings.yaml index c2234fbd27f..6b8322675f0 100644 --- a/config/crds/apis.kcp.io_apibindings.yaml +++ b/config/crds/apis.kcp.io_apibindings.yaml @@ -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: @@ -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: diff --git a/pkg/admission/initializers/initializer.go b/pkg/admission/initializers/initializer.go index 8e464b7cb57..ab2b29a4c67 100644 --- a/pkg/admission/initializers/initializer.go +++ b/pkg/admission/initializers/initializer.go @@ -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" ) @@ -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) + } +} diff --git a/pkg/admission/initializers/interfaces.go b/pkg/admission/initializers/interfaces.go index 37cfe381622..bfaedd685f3 100644 --- a/pkg/admission/initializers/interfaces.go +++ b/pkg/admission/initializers/interfaces.go @@ -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" ) @@ -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) +} diff --git a/pkg/admission/permissionclaims/mutating_permission_claims.go b/pkg/admission/permissionclaims/mutating_permission_claims.go index 2f5fa1e7983..a5c8dd40afc 100644 --- a/pkg/admission/permissionclaims/mutating_permission_claims.go +++ b/pkg/admission/permissionclaims/mutating_permission_claims.go @@ -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" ) @@ -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 } @@ -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 { @@ -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 } @@ -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 } @@ -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 +} diff --git a/pkg/openapi/zz_generated.openapi.go b/pkg/openapi/zz_generated.openapi.go index a089af5c43e..86574c4db87 100644 --- a/pkg/openapi/zz_generated.openapi.go +++ b/pkg/openapi/zz_generated.openapi.go @@ -84,6 +84,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.LocalAPIExportPolicy": schema_sdk_apis_apis_v1alpha2_LocalAPIExportPolicy(ref), "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.MaximalPermissionPolicy": schema_sdk_apis_apis_v1alpha2_MaximalPermissionPolicy(ref), "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaim": schema_sdk_apis_apis_v1alpha2_PermissionClaim(ref), + "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaimJSONPathReference": schema_sdk_apis_apis_v1alpha2_PermissionClaimJSONPathReference(ref), + "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaimReference": schema_sdk_apis_apis_v1alpha2_PermissionClaimReference(ref), "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaimSelector": schema_sdk_apis_apis_v1alpha2_PermissionClaimSelector(ref), "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.ResourceSchema": schema_sdk_apis_apis_v1alpha2_ResourceSchema(ref), "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.ResourceSchemaStorage": schema_sdk_apis_apis_v1alpha2_ResourceSchemaStorage(ref), @@ -2688,6 +2690,73 @@ func schema_sdk_apis_apis_v1alpha2_PermissionClaim(ref common.ReferenceCallback) } } +func schema_sdk_apis_apis_v1alpha2_PermissionClaimJSONPathReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PermissionClaimJSONPathReference uses JSONPath expressions to select a name and optionally a namespace in an unstructured object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "namespace is the JSONPath to select the referent object namespace.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the JSONPath to select the referent object name.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_sdk_apis_apis_v1alpha2_PermissionClaimReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PermissionClaimReference describes an object by specifying in what bound resource it is named.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "group is the API group of the bound resource in which the reference must occur.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource is the bound resource in which the reference must occur.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "jsonPath": { + SchemaProps: spec.SchemaProps{ + Description: "jsonPath uses JSONPath expressions to select the referent.", + Ref: ref("github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaimJSONPathReference"), + }, + }, + }, + Required: []string{"group", "resource"}, + }, + }, + Dependencies: []string{ + "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaimJSONPathReference"}, + } +} + func schema_sdk_apis_apis_v1alpha2_PermissionClaimSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2730,6 +2799,20 @@ func schema_sdk_apis_apis_v1alpha2_PermissionClaimSelector(ref common.ReferenceC }, }, }, + "references": { + SchemaProps: spec.SchemaProps{ + Description: "references is scoping the access to claimed objects down to those that are explicitly named in fields in the bound resources.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaimReference"), + }, + }, + }, + }, + }, "matchAll": { SchemaProps: spec.SchemaProps{ Description: "matchAll grants access to all objects of the claimed resource.", @@ -2741,7 +2824,7 @@ func schema_sdk_apis_apis_v1alpha2_PermissionClaimSelector(ref common.ReferenceC }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"}, + "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2.PermissionClaimReference", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"}, } } diff --git a/pkg/permissionclaim/permissionclaim_labeler.go b/pkg/permissionclaim/permissionclaim_labeler.go index 5146b1cfa72..aeb850b81bd 100644 --- a/pkg/permissionclaim/permissionclaim_labeler.go +++ b/pkg/permissionclaim/permissionclaim_labeler.go @@ -18,20 +18,25 @@ package permissionclaim import ( "context" + "errors" "fmt" + kcpdynamic "github.com/kcp-dev/client-go/dynamic" authenticationv1 "k8s.io/api/authentication/v1" authorizationv1 "k8s.io/api/authorization/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" klabels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/jsonpath" "k8s.io/klog/v2" "github.com/kcp-dev/logicalcluster/v3" "github.com/kcp-dev/kcp/pkg/indexers" "github.com/kcp-dev/kcp/pkg/logging" + "github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper" "github.com/kcp-dev/kcp/sdk/apis/apis" apisv1alpha2 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2" "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2/permissionclaims" @@ -52,6 +57,9 @@ var NonPersistedResourcesClaimable = map[schema.GroupResource]bool{ // Labeler calculates labels to apply to all instances of a cluster-group-resource based on permission claims. type Labeler struct { + dynRESTMapper *dynamicrestmapper.DynamicRESTMapper + dynClusterClient kcpdynamic.ClusterInterface + listAPIBindingsAcceptingClaimedGroupResource func(clusterName logicalcluster.Name, groupResource schema.GroupResource) ([]*apisv1alpha2.APIBinding, error) getAPIBinding func(clusterName logicalcluster.Name, name string) (*apisv1alpha2.APIBinding, error) getAPIExport func(path logicalcluster.Path, name string) (*apisv1alpha2.APIExport, error) @@ -61,8 +69,13 @@ type Labeler struct { func NewLabeler( apiBindingInformer apisv1alpha2informers.APIBindingClusterInformer, apiExportInformer, globalAPIExportInformer apisv1alpha2informers.APIExportClusterInformer, + dynRESTMapper *dynamicrestmapper.DynamicRESTMapper, + dynClusterClient kcpdynamic.ClusterInterface, ) *Labeler { return &Labeler{ + dynRESTMapper: dynRESTMapper, + dynClusterClient: dynClusterClient, + listAPIBindingsAcceptingClaimedGroupResource: func(clusterName logicalcluster.Name, groupResource schema.GroupResource) ([]*apisv1alpha2.APIBinding, error) { indexKey := indexers.ClusterAndGroupResourceValue(clusterName, groupResource) return indexers.ByIndex[*apisv1alpha2.APIBinding](apiBindingInformer.Informer().GetIndexer(), indexers.APIBindingByClusterAndAcceptedClaimedGroupResources, indexKey) @@ -90,7 +103,7 @@ func normalizeEventGroupResource(gr schema.GroupResource) schema.GroupResource { // LabelsFor returns all the applicable labels for the cluster-group-resource relating to permission claims. This is // the intersection of (1) all APIBindings in the cluster that have accepted claims for the group-resource with (2) // associated APIExports that are claiming group-resource. -func (l *Labeler) LabelsFor(ctx context.Context, cluster logicalcluster.Name, groupResource schema.GroupResource, resourceName string, resourceLabels map[string]string) (map[string]string, error) { +func (l *Labeler) LabelsFor(ctx context.Context, cluster logicalcluster.Name, groupResource schema.GroupResource, claimedObject *unstructured.Unstructured) (map[string]string, error) { labels := map[string]string{} if _, nonPersisted := NonPersistedResourcesClaimable[groupResource]; nonPersisted { return labels, nil @@ -123,24 +136,19 @@ func (l *Labeler) LabelsFor(ctx context.Context, cluster logicalcluster.Name, gr continue } - if !claim.Selector.MatchAll { - selector, err := metav1.LabelSelectorAsSelector(&claim.Selector.LabelSelector) - if err != nil { - logger.Error(err, "error calculating permission claim label key and value", - "claim", claim.String()) - continue - } - - if !selector.Matches(klabels.Set(resourceLabels)) { - continue - } + claimLogger := logger.WithValues("claim", claim.String()) + + if match, err := l.claimMatchesObject(ctx, cluster, &claim, claimedObject); err != nil { + claimLogger.Error(err, "error matching object") + continue + } else if !match { + continue } k, v, err := permissionclaims.ToLabelKeyAndValue(logicalcluster.From(export), export.Name, claim.PermissionClaim) if err != nil { // extremely unlikely to get an error here - it means the json marshaling failed - logger.Error(err, "error calculating permission claim label key and value", - "claim", claim.String()) + claimLogger.Error(err, "error calculating permission claim label key and value") continue } labels[k] = v @@ -151,9 +159,9 @@ func (l *Labeler) LabelsFor(ctx context.Context, cluster logicalcluster.Name, gr // pointing to an APIExport visible to the owner of the export, independently of the permission claim // acceptance of the binding. if groupResource.Group == apis.GroupName && groupResource.Resource == "apibindings" { - binding, err := l.getAPIBinding(cluster, resourceName) + binding, err := l.getAPIBinding(cluster, claimedObject.GetName()) if err != nil { - logger.Error(err, "error getting APIBinding", "bindingName", resourceName) + logger.Error(err, "error getting APIBinding", "bindingName", claimedObject.GetName()) return labels, nil // can only be a NotFound } @@ -173,6 +181,99 @@ func (l *Labeler) LabelsFor(ctx context.Context, cluster logicalcluster.Name, gr return labels, nil } +func (l *Labeler) claimMatchesObject(ctx context.Context, cluster logicalcluster.Name, claim *apisv1alpha2.AcceptablePermissionClaim, obj *unstructured.Unstructured) (bool, error) { + if claim.Selector.MatchAll { + return true, nil + } + + if sel := claim.Selector.LabelSelector; len(sel.MatchLabels) > 0 || len(sel.MatchExpressions) > 0 { + selector, err := metav1.LabelSelectorAsSelector(&sel) + if err != nil { + return false, fmt.Errorf("error calculating permission claim label key and value: %w", err) + } + + return selector.Matches(klabels.Set(obj.GetLabels())), nil + } + + for _, ref := range claim.Selector.References { + match, err := l.referenceMatchesObject(ctx, cluster, ref, obj) + if match || err != nil { + return match, err + } + } + + return false, nil +} + +func (l *Labeler) referenceMatchesObject(ctx context.Context, cluster logicalcluster.Name, ref apisv1alpha2.PermissionClaimReference, obj *unstructured.Unstructured) (bool, error) { + // invalid objects should never have been admitted in the first place, this is just to catch possible panics + if ref.JSONPath == nil { + return false, errors.New("invalid claim: no JSONPath specified") + } + + nameExpr := jsonpath.New("name").AllowMissingKeys(true) + if err := nameExpr.Parse(ref.JSONPath.NamePath); err != nil { + return false, fmt.Errorf("invalid claim: invalid JSONPath name expression: %w", err) + } + + var namespaceExpr *jsonpath.JSONPath + if ref.JSONPath.NamespacePath != "" { + namespaceExpr = jsonpath.New("namespace").AllowMissingKeys(true) + if err := namespaceExpr.Parse(ref.JSONPath.NamespacePath); err != nil { + return false, fmt.Errorf("invalid claim: invalid JSONPath namespace expression: %w", err) + } + } + + gvrs, err := l.dynRESTMapper.ForCluster(cluster).ResourcesFor(schema.GroupVersionResource{ + Group: ref.Group, + Resource: ref.Resource, + // Version is left blank to let the mapper decide + }) + if err != nil { + return false, fmt.Errorf("failed to resolve group resource %s/%s: %w", ref.Group, ref.Resource, err) + } + + allObjects, err := l.dynClusterClient.Cluster(cluster.Path()).Resource(gvrs[0]).List(ctx, metav1.ListOptions{}) + if err != nil { + return false, fmt.Errorf("failed to list referring resource: %w", err) + } + + for _, referringObject := range allObjects.Items { + results, err := nameExpr.FindResults(referringObject.Object) + if err != nil { + return false, fmt.Errorf("failed to apply JSONPath: %w", err) + } + + if l := len(results); l == 0 { + continue + } else if l > 1 { + return false, fmt.Errorf("invalid JSONPath, expected one result, got %d", l) + } + + result := results[0] + if l := len(result); l == 0 { + continue + } else if l > 1 { + return false, fmt.Errorf("invalid JSONPath, expected one sub result, got %d", l) + } + + resultValue, ok := result[0].Interface().(string) + if !ok { + // anything but a string doesn't mean the JSONPath is broken, but that the + // object might be unexpected + continue + } + + fmt.Printf("value: %q\n", resultValue) + + // TODO: do the same for the namespace, implement the matching from the found + // values to what is in the APIBinding, handle cluster-scoped to namspace-scoped + // referencing. + } + + return false, nil +} + // InstallIndexers adds the additional indexers that this controller requires to the informers. func InstallIndexers(apiExportInformer apisv1alpha2informers.APIExportClusterInformer) { indexers.AddIfNotPresentOrDie(apiExportInformer.Informer().GetIndexer(), cache.Indexers{ diff --git a/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_controller.go b/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_controller.go index 95a04046861..2c80265ae6d 100644 --- a/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_controller.go +++ b/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_controller.go @@ -38,6 +38,7 @@ import ( "github.com/kcp-dev/kcp/pkg/informer" "github.com/kcp-dev/kcp/pkg/logging" "github.com/kcp-dev/kcp/pkg/permissionclaim" + "github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper" kcpclientset "github.com/kcp-dev/kcp/sdk/client/clientset/versioned/cluster" apisv1alpha2informers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions/apis/v1alpha2" ) @@ -50,6 +51,7 @@ const ( func NewResourceController( kcpClusterClient kcpclientset.ClusterInterface, dynamicClusterClient kcpdynamic.ClusterInterface, + dynamicRESTMapper *dynamicrestmapper.DynamicRESTMapper, dynamicDiscoverySharedInformerFactory *informer.DiscoveringDynamicSharedInformerFactory, apiBindingInformer apisv1alpha2informers.APIBindingClusterInformer, apiExportInformer, globalAPIExportInformer apisv1alpha2informers.APIExportClusterInformer, @@ -64,7 +66,7 @@ func NewResourceController( kcpClusterClient: kcpClusterClient, dynamicClusterClient: dynamicClusterClient, ddsif: dynamicDiscoverySharedInformerFactory, - permissionClaimLabeler: permissionclaim.NewLabeler(apiBindingInformer, apiExportInformer, globalAPIExportInformer), + permissionClaimLabeler: permissionclaim.NewLabeler(apiBindingInformer, apiExportInformer, globalAPIExportInformer, dynamicRESTMapper, dynamicClusterClient), } logger := logging.WithReconciler(klog.Background(), ControllerName) diff --git a/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_reconcile.go b/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_reconcile.go index 802ce8c70ab..eca722e613b 100644 --- a/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_reconcile.go +++ b/pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_reconcile.go @@ -41,7 +41,7 @@ func (c *resourceController) reconcile(ctx context.Context, obj *unstructured.Un logger := klog.FromContext(ctx) clusterName := logicalcluster.From(obj) - expectedLabels, err := c.permissionClaimLabeler.LabelsFor(ctx, clusterName, gvr.GroupResource(), obj.GetName(), obj.GetLabels()) + expectedLabels, err := c.permissionClaimLabeler.LabelsFor(ctx, clusterName, gvr.GroupResource(), obj) if err != nil { return fmt.Errorf("error calculating permission claim labels for GVR %q %s/%s: %w", gvr, obj.GetNamespace(), obj.GetName(), err) } diff --git a/pkg/server/config.go b/pkg/server/config.go index 48384f67696..70869b61c08 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -65,6 +65,7 @@ import ( "github.com/kcp-dev/kcp/pkg/indexers" "github.com/kcp-dev/kcp/pkg/informer" "github.com/kcp-dev/kcp/pkg/network" + "github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper" "github.com/kcp-dev/kcp/pkg/server/aggregatingcrdversiondiscovery" "github.com/kcp-dev/kcp/pkg/server/bootstrap" kcpfilters "github.com/kcp-dev/kcp/pkg/server/filters" @@ -141,6 +142,12 @@ type ExtraConfig struct { CacheDiscoveringDynamicSharedInformerFactory *informer.DiscoveringDynamicSharedInformerFactory CacheKcpSharedInformerFactory kcpinformers.SharedInformerFactory CacheKubeSharedInformerFactory kcpkubernetesinformers.SharedInformerFactory + + // DynRESTMapper is a workspace-aware REST mapper, backed by a reconciler, + // which dynamically loads all bound resources through every type associated + // with an APIBinding in the workspace into the mapper. Another controller can + // use this to resolve the Kind/Resource of the objects. + DynRESTMapper *dynamicrestmapper.DynamicRESTMapper } type completedConfig struct { @@ -193,6 +200,9 @@ const KcpBootstrapperUserName = "system:kcp:bootstrapper" func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Config, error) { c := &Config{ Options: opts, + ExtraConfig: ExtraConfig{ + DynRESTMapper: dynamicrestmapper.NewDynamicRESTMapper(), + }, } if opts.Extra.ProfilerAddress != "" { @@ -566,6 +576,7 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co kcpadmissioninitializers.NewKubeQuotaConfigurationInitializer(quotaConfiguration), kcpadmissioninitializers.NewServerShutdownInitializer(c.quotaAdmissionStopCh), kcpadmissioninitializers.NewDynamicClusterClientInitializer(c.DynamicClusterClient), + kcpadmissioninitializers.NewDynamicRESTMapperInitializer(c.DynRESTMapper), } c.ShardBaseURL = func() string { diff --git a/pkg/server/controllers.go b/pkg/server/controllers.go index 6774d66cb9d..c776ae90287 100644 --- a/pkg/server/controllers.go +++ b/pkg/server/controllers.go @@ -845,6 +845,7 @@ func (s *Server) installAPIBindingController(ctx context.Context, config *rest.C permissionClaimLabelResourceController, err := permissionclaimlabel.NewResourceController( kcpClusterClient, dynamicClusterClient, + s.DynRESTMapper, ddsif, s.KcpSharedInformerFactory.Apis().V1alpha2().APIBindings(), s.KcpSharedInformerFactory.Apis().V1alpha2().APIExports(), diff --git a/pkg/server/server.go b/pkg/server/server.go index 9aa0916da63..6891e06d75d 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -57,7 +57,6 @@ import ( "github.com/kcp-dev/kcp/pkg/informer" metadataclient "github.com/kcp-dev/kcp/pkg/metadata" "github.com/kcp-dev/kcp/pkg/reconciler/cache/replication" - "github.com/kcp-dev/kcp/pkg/reconciler/dynamicrestmapper" "github.com/kcp-dev/kcp/pkg/reconciler/kubequota" "github.com/kcp-dev/kcp/pkg/server/aggregatingcrdversiondiscovery" "github.com/kcp-dev/kcp/pkg/server/options/batteries" @@ -80,11 +79,6 @@ type Server struct { AggregatingCRDVersionDiscovery *aggregatingcrdversiondiscovery.Server MiniAggregator *miniaggregator.MiniAggregatorServer virtual *virtualrootapiserver.Server - // DynRESTMapper is a workspace-aware REST mapper, backed by a reconciler, - // which dynamically loads all bound resources through every type associated - // with an APIBinding in the workspace into the mapper. Another controller can - // use this to resolve the Kind/Resource of the objects. - DynRESTMapper *dynamicrestmapper.DynamicRESTMapper syncedCh chan struct{} rootPhase1FinishedCh chan struct{} @@ -106,7 +100,6 @@ func NewServer(c CompletedConfig) (*Server, error) { syncedCh: make(chan struct{}), rootPhase1FinishedCh: make(chan struct{}), controllers: make(map[string]*controllerWrapper), - DynRESTMapper: dynamicrestmapper.NewDynamicRESTMapper(), } notFoundHandler := notfoundhandler.New(c.GenericConfig.Serializer, genericapifilters.NoMuxAndDiscoveryIncompleteKey) diff --git a/pkg/virtual/apiexport/authorizer/binding.go b/pkg/virtual/apiexport/authorizer/binding.go index dde38cbbbd0..ce46dc59507 100644 --- a/pkg/virtual/apiexport/authorizer/binding.go +++ b/pkg/virtual/apiexport/authorizer/binding.go @@ -116,33 +116,28 @@ func (a *boundAPIAuthorizer) Authorize(ctx context.Context, attr authorizer.Attr } } - // check if a resource claim for this resource has been accepted and has correct verbs. - for _, permissionClaim := range apiBinding.Spec.PermissionClaims { - if permissionClaim.State != apisv1alpha2.ClaimAccepted { + // Check if a resource claim for this resource has been accepted, has the correct verbs and is + // referenced in one of the objects defined by the provided references (if no references are + // given in a permissionClaim, all objects should be accessible). + for _, boundClaim := range apiBinding.Spec.PermissionClaims { + if boundClaim.State != apisv1alpha2.ClaimAccepted { // if the claim is not accepted it cannot be used. continue } - if permissionClaim.Group == attr.GetAPIGroup() && permissionClaim.Resource == attr.GetResource() { - apiBindingVerbs := sets.New(permissionClaim.Verbs...) - apiExportVerbs := sets.New[string]() - - for _, exportPermpermissionClaim := range apiExport.Spec.PermissionClaims { - if exportPermpermissionClaim.EqualGRI(permissionClaim.PermissionClaim) { - apiExportVerbs.Insert(exportPermpermissionClaim.Verbs...) - - break + // found a (not necessarily *the*) claim for this resource in the binding; however there + // should under normal circumstances only be one matching claim. + if boundClaim.Group == attr.GetAPIGroup() && boundClaim.Resource == attr.GetResource() { + // find all matching exported claims (usually this should also just be one) + for _, exportedClaim := range apiExport.Spec.PermissionClaims { + if exportedClaim.EqualGRI(boundClaim.PermissionClaim) { + if permissionClaimAllowsVerb(&boundClaim, &exportedClaim, attr) { + return a.delegate.Authorize(ctx, attr) + } + + // ... } } - - allowedVerbs := apiBindingVerbs.Intersection(apiExportVerbs) - - if !allowedVerbs.HasAny(attr.GetVerb(), wildcardVerb) { - // if the requested verb is not found, the claim cannot be used. - continue - } - - return a.delegate.Authorize(ctx, attr) } } @@ -160,3 +155,11 @@ func (a *boundAPIAuthorizer) Authorize(ctx context.Context, attr authorizer.Attr // The APIExport owner has not been invited in. return authorizer.DecisionDeny, "failed to find suitable reason to allow access in APIBinding", nil } + +func permissionClaimAllowsVerb(boundClaim *apisv1alpha2.AcceptablePermissionClaim, exportedClaim *apisv1alpha2.PermissionClaim, attr authorizer.Attributes) bool { + boundVerbs := sets.New(boundClaim.Verbs...) + exportedVerbs := sets.New(exportedClaim.Verbs...) + allowedVerbs := boundVerbs.Intersection(exportedVerbs) + + return allowedVerbs.HasAny(attr.GetVerb(), wildcardVerb) +} diff --git a/sdk/apis/apis/v1alpha2/types_apibinding.go b/sdk/apis/apis/v1alpha2/types_apibinding.go index 0044d8f462a..f4cef77282c 100644 --- a/sdk/apis/apis/v1alpha2/types_apibinding.go +++ b/sdk/apis/apis/v1alpha2/types_apibinding.go @@ -120,10 +120,35 @@ const ( type PermissionClaimSelector struct { metav1.LabelSelector `json:",inline"` + // references is scoping the access to claimed objects down to those that + // are explicitly named in fields in the bound resources. + References []PermissionClaimReference `json:"references,omitempty"` + // matchAll grants access to all objects of the claimed resource. MatchAll bool `json:"matchAll,omitempty"` } +// PermissionClaimReference describes an object by specifying in what bound resource +// it is named. +type PermissionClaimReference struct { + // group is the API group of the bound resource in which the reference must occur. + Group string `json:"group"` + // resource is the bound resource in which the reference must occur. + Resource string `json:"resource"` + + // jsonPath uses JSONPath expressions to select the referent. + JSONPath *PermissionClaimJSONPathReference `json:"jsonPath,omitempty"` +} + +// PermissionClaimJSONPathReference uses JSONPath expressions to select a name and +// optionally a namespace in an unstructured object. +type PermissionClaimJSONPathReference struct { + // namespace is the JSONPath to select the referent object namespace. + NamespacePath string `json:"namespace,omitempty"` + // name is the JSONPath to select the referent object name. + NamePath string `json:"name"` +} + // BindingReference describes a reference to an APIExport. Exactly one of the // fields must be set. type BindingReference struct { diff --git a/sdk/apis/apis/v1alpha2/validation.go b/sdk/apis/apis/v1alpha2/validation.go index e6ec06dcdcc..0829b4dedcb 100644 --- a/sdk/apis/apis/v1alpha2/validation.go +++ b/sdk/apis/apis/v1alpha2/validation.go @@ -70,15 +70,28 @@ func ValidateAPIBindingPermissionClaims(permissionClaims []AcceptablePermissionC for i := range permissionClaims { claimPath := path.Index(i) - if permissionClaims[i].Selector.MatchAll { + switch { + case permissionClaims[i].Selector.MatchAll: if len(permissionClaims[i].Selector.MatchLabels) > 0 { allErrs = append(allErrs, field.Invalid(claimPath.Child("selector").Child("matchLabels"), permissionClaims[i].Selector, "matchLabels cannot be used with matchAll")) } if len(permissionClaims[i].Selector.MatchExpressions) > 0 { allErrs = append(allErrs, field.Invalid(claimPath.Child("selector").Child("matchExpressions"), permissionClaims[i].Selector, "matchExpressions cannot be used with matchAll")) } - } else if len(permissionClaims[i].Selector.MatchLabels) == 0 && len(permissionClaims[i].Selector.MatchExpressions) == 0 { - allErrs = append(allErrs, field.Required(claimPath.Child("selector"), "either one of matchAll, matchLabels, or matchExpressions must be set")) + if len(permissionClaims[i].Selector.References) > 0 { + allErrs = append(allErrs, field.Invalid(claimPath.Child("selector").Child("references"), permissionClaims[i].Selector, "references cannot be used with matchAll")) + } + + case len(permissionClaims[i].Selector.MatchLabels) > 0 || len(permissionClaims[i].Selector.MatchExpressions) > 0: + if len(permissionClaims[i].Selector.References) > 0 { + allErrs = append(allErrs, field.Invalid(claimPath.Child("selector").Child("references"), permissionClaims[i].Selector, "matchLabels/matchExpressions cannot be used with matchAll")) + } + + case len(permissionClaims[i].Selector.References) > 0: + // NOP + + default: + allErrs = append(allErrs, field.Required(claimPath.Child("selector"), "either one of matchAll, matchLabels, matchExpressions or references must be set")) } } diff --git a/sdk/apis/apis/v1alpha2/zz_generated.deepcopy.go b/sdk/apis/apis/v1alpha2/zz_generated.deepcopy.go index acefe38abbf..9f08f8faeb4 100644 --- a/sdk/apis/apis/v1alpha2/zz_generated.deepcopy.go +++ b/sdk/apis/apis/v1alpha2/zz_generated.deepcopy.go @@ -474,10 +474,54 @@ func (in *PermissionClaim) DeepCopy() *PermissionClaim { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PermissionClaimJSONPathReference) DeepCopyInto(out *PermissionClaimJSONPathReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PermissionClaimJSONPathReference. +func (in *PermissionClaimJSONPathReference) DeepCopy() *PermissionClaimJSONPathReference { + if in == nil { + return nil + } + out := new(PermissionClaimJSONPathReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PermissionClaimReference) DeepCopyInto(out *PermissionClaimReference) { + *out = *in + if in.JSONPath != nil { + in, out := &in.JSONPath, &out.JSONPath + *out = new(PermissionClaimJSONPathReference) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PermissionClaimReference. +func (in *PermissionClaimReference) DeepCopy() *PermissionClaimReference { + if in == nil { + return nil + } + out := new(PermissionClaimReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PermissionClaimSelector) DeepCopyInto(out *PermissionClaimSelector) { *out = *in in.LabelSelector.DeepCopyInto(&out.LabelSelector) + if in.References != nil { + in, out := &in.References, &out.References + *out = make([]PermissionClaimReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimjsonpathreference.go b/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimjsonpathreference.go new file mode 100644 index 00000000000..782caeb58b1 --- /dev/null +++ b/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimjsonpathreference.go @@ -0,0 +1,48 @@ +/* +Copyright The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// PermissionClaimJSONPathReferenceApplyConfiguration represents a declarative configuration of the PermissionClaimJSONPathReference type for use +// with apply. +type PermissionClaimJSONPathReferenceApplyConfiguration struct { + NamespacePath *string `json:"namespace,omitempty"` + NamePath *string `json:"name,omitempty"` +} + +// PermissionClaimJSONPathReferenceApplyConfiguration constructs a declarative configuration of the PermissionClaimJSONPathReference type for use with +// apply. +func PermissionClaimJSONPathReference() *PermissionClaimJSONPathReferenceApplyConfiguration { + return &PermissionClaimJSONPathReferenceApplyConfiguration{} +} + +// WithNamespacePath sets the NamespacePath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NamespacePath field is set to the value of the last call. +func (b *PermissionClaimJSONPathReferenceApplyConfiguration) WithNamespacePath(value string) *PermissionClaimJSONPathReferenceApplyConfiguration { + b.NamespacePath = &value + return b +} + +// WithNamePath sets the NamePath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NamePath field is set to the value of the last call. +func (b *PermissionClaimJSONPathReferenceApplyConfiguration) WithNamePath(value string) *PermissionClaimJSONPathReferenceApplyConfiguration { + b.NamePath = &value + return b +} diff --git a/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimreference.go b/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimreference.go new file mode 100644 index 00000000000..ad4095c0814 --- /dev/null +++ b/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimreference.go @@ -0,0 +1,57 @@ +/* +Copyright The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// PermissionClaimReferenceApplyConfiguration represents a declarative configuration of the PermissionClaimReference type for use +// with apply. +type PermissionClaimReferenceApplyConfiguration struct { + Group *string `json:"group,omitempty"` + Resource *string `json:"resource,omitempty"` + JSONPath *PermissionClaimJSONPathReferenceApplyConfiguration `json:"jsonPath,omitempty"` +} + +// PermissionClaimReferenceApplyConfiguration constructs a declarative configuration of the PermissionClaimReference type for use with +// apply. +func PermissionClaimReference() *PermissionClaimReferenceApplyConfiguration { + return &PermissionClaimReferenceApplyConfiguration{} +} + +// WithGroup sets the Group field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Group field is set to the value of the last call. +func (b *PermissionClaimReferenceApplyConfiguration) WithGroup(value string) *PermissionClaimReferenceApplyConfiguration { + b.Group = &value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *PermissionClaimReferenceApplyConfiguration) WithResource(value string) *PermissionClaimReferenceApplyConfiguration { + b.Resource = &value + return b +} + +// WithJSONPath sets the JSONPath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the JSONPath field is set to the value of the last call. +func (b *PermissionClaimReferenceApplyConfiguration) WithJSONPath(value *PermissionClaimJSONPathReferenceApplyConfiguration) *PermissionClaimReferenceApplyConfiguration { + b.JSONPath = value + return b +} diff --git a/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimselector.go b/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimselector.go index d05702c3b96..9dc17e089fb 100644 --- a/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimselector.go +++ b/sdk/client/applyconfiguration/apis/v1alpha2/permissionclaimselector.go @@ -26,7 +26,8 @@ import ( // with apply. type PermissionClaimSelectorApplyConfiguration struct { v1.LabelSelectorApplyConfiguration `json:",inline"` - MatchAll *bool `json:"matchAll,omitempty"` + References []PermissionClaimReferenceApplyConfiguration `json:"references,omitempty"` + MatchAll *bool `json:"matchAll,omitempty"` } // PermissionClaimSelectorApplyConfiguration constructs a declarative configuration of the PermissionClaimSelector type for use with @@ -62,6 +63,19 @@ func (b *PermissionClaimSelectorApplyConfiguration) WithMatchExpressions(values return b } +// WithReferences adds the given value to the References field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the References field. +func (b *PermissionClaimSelectorApplyConfiguration) WithReferences(values ...*PermissionClaimReferenceApplyConfiguration) *PermissionClaimSelectorApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithReferences") + } + b.References = append(b.References, *values[i]) + } + return b +} + // WithMatchAll sets the MatchAll field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MatchAll field is set to the value of the last call. diff --git a/sdk/client/applyconfiguration/utils.go b/sdk/client/applyconfiguration/utils.go index c07452c2761..efdbf898c43 100644 --- a/sdk/client/applyconfiguration/utils.go +++ b/sdk/client/applyconfiguration/utils.go @@ -142,6 +142,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apisv1alpha2.MaximalPermissionPolicyApplyConfiguration{} case v1alpha2.SchemeGroupVersion.WithKind("PermissionClaim"): return &apisv1alpha2.PermissionClaimApplyConfiguration{} + case v1alpha2.SchemeGroupVersion.WithKind("PermissionClaimJSONPathReference"): + return &apisv1alpha2.PermissionClaimJSONPathReferenceApplyConfiguration{} + case v1alpha2.SchemeGroupVersion.WithKind("PermissionClaimReference"): + return &apisv1alpha2.PermissionClaimReferenceApplyConfiguration{} case v1alpha2.SchemeGroupVersion.WithKind("PermissionClaimSelector"): return &apisv1alpha2.PermissionClaimSelectorApplyConfiguration{} case v1alpha2.SchemeGroupVersion.WithKind("ResourceSchema"):