Skip to content

Commit 280844c

Browse files
authored
Merge pull request #3494 from xmudrii/vw-admission
Implement admission for Virtual Workspaces and label selectors for PermissionClaims
2 parents e734ff2 + 97deb88 commit 280844c

File tree

28 files changed

+2067
-140
lines changed

28 files changed

+2067
-140
lines changed

cmd/virtual-workspaces/command/cmd.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ func Run(ctx context.Context, o *options.Options) error {
178178
return err
179179
}
180180

181+
if err := o.VirtualWorkspaceAdmission.ApplyTo(&recommendedConfig.Config, func() []virtualrootapiserver.NamedVirtualWorkspace {
182+
return rootAPIServerConfig.Extra.VirtualWorkspaces
183+
}); err != nil {
184+
return err
185+
}
186+
181187
sharedExternalURLGetter := func() string {
182188
return o.ShardExternalURL
183189
}

cmd/virtual-workspaces/options/options.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ type Options struct {
5555

5656
Logs *logs.Options
5757

58-
CoreVirtualWorkspaces corevwoptions.Options
58+
CoreVirtualWorkspaces corevwoptions.Options
59+
VirtualWorkspaceAdmission corevwoptions.Admission
5960

6061
ProfilerAddress string
6162
}
@@ -74,8 +75,9 @@ func NewOptions() *Options {
7475
Audit: *genericapiserveroptions.NewAuditOptions(),
7576
Logs: logs.NewOptions(),
7677

77-
CoreVirtualWorkspaces: *corevwoptions.NewOptions(),
78-
ProfilerAddress: "",
78+
CoreVirtualWorkspaces: *corevwoptions.NewOptions(),
79+
VirtualWorkspaceAdmission: *corevwoptions.NewAdmission(),
80+
ProfilerAddress: "",
7981
}
8082

8183
opts.SecureServing.ServerCert.CertKey.CertFile = filepath.Join(".", ".kcp", "apiserver.crt")

config/crds/apis.kcp.io_apibindings.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,6 @@ spec:
557557
x-kubernetes-validations:
558558
- message: Permission claim selector is immutable
559559
rule: self == oldSelf
560-
- message: a selector is required. Only "matchAll" is currently
561-
implemented
562-
rule: (has(self.matchAll) && self.matchAll)
563560
state:
564561
enum:
565562
- Accepted
@@ -710,9 +707,6 @@ spec:
710707
x-kubernetes-validations:
711708
- message: Permission claim selector is immutable
712709
rule: self == oldSelf
713-
- message: a selector is required. Only "matchAll" is currently
714-
implemented
715-
rule: (has(self.matchAll) && self.matchAll)
716710
verbs:
717711
description: |-
718712
verbs is a list of supported API operation types (this includes

docs/content/concepts/apis/exporting-apis.md

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,12 @@ served to clients. See [Build Your Controller](#build-your-controller) for more
177177
When a consumer creates an `APIBinding` that binds to an `APIExport`, the API provider who owns the `APIExport`
178178
implicitly has access to instances of the exported APIs in the consuming workspace. There are also times when the API
179179
provider needs to access additional resource data in a consuming workspace. These resources might come from other
180-
APIExports the consumer has created `APIBindings` for, or from APIs that are built in to kcp. The API provider
181-
requests access to these additional resources by adding `PermissionClaims` for the desired API's group, resource, and
182-
identity hash to their `APIExport`. The API provider is responsible for specifying what operations are required for the
183-
requested resource by setting the appropriate [API verbs](https://kubernetes.io/docs/reference/using-api/api-concepts/#api-verbs).
180+
APIExports the consumer has created `APIBindings` for, or from APIs that are built in to kcp.
181+
182+
The API provider requests access to these additional resources by adding `PermissionClaims` for the desired API's
183+
group, resource, and identity hash to their `APIExport`. The API provider is responsible for specifying what operations
184+
are required for the requested resource by setting the appropriate
185+
[API verbs](https://kubernetes.io/docs/reference/using-api/api-concepts/#api-verbs).
184186
Let's take the example `APIExport` from above and add permission claims for `ConfigMaps` and `Things`:
185187

186188
```yaml
@@ -211,10 +213,18 @@ spec:
211213
4. `"*"` is a special "verb" that matches any possible verb
212214

213215
This is essentially a request from the API provider, asking each consumer to grant permission for the claimed
214-
resources. If the consumer does not accept a permission claim, the API provider is not allowed to access the claimed
215-
resources. Consumer acceptance of permission claims is part of the `APIBinding` spec. The operations allowed on the
216-
resource are the intersection of the verbs defined in the `APIExport` and the verbs accepted in the appropriate
217-
`APIBinding`. For more details, see the section on [APIBindings](#apibinding).
216+
resources. The consumer can, via `APIBinding`, accept or reject the service provider's request to access these
217+
resources, i.e. accept or reject PermissionClaims.
218+
219+
Additionally, the consumer can choose between giving access to all instances (objects) of a claimed resource
220+
and giving access only to a subset of instances. In the latter case, the consumer can specify labels when accepting
221+
the PermissionClaim, so the service provider can only access instances which have these specified labels
222+
(this is also known as a label selector).
223+
224+
The set of operations that the service provider can perform on the claimed resource is the intersection of the verbs
225+
defined in the `APIExport` and the verbs accepted in the appropriate `APIBinding`.
226+
227+
For more details, see the section on [APIBindings](#apibinding).
218228

219229
### Maximal Permission Policy
220230

@@ -420,8 +430,13 @@ spec:
420430

421431
#### Permission Claims
422432

423-
Furthermore, `APIBindings` provide the `APIExport` owner access to additional resources defined in an `APIExport`'s permission claims list. Permission claims must be accepted by the user explicitly, before this access is granted. The resources can be builtin Kubernetes resources or resources from other `APIExports`.
424-
When an `APIExport` is changed after workspaces have bound to it, new or changed APIs are automatically propagated to all `APIBindings`. New permission claims on the other hand are NOT automatically accepted.
433+
Furthermore, `APIBindings` provide the `APIExport` owner access to additional resources defined in an `APIExport`'s
434+
permission claims list. Permission claims must be accepted by the user explicitly, before this access is granted.
435+
The resources can be builtin Kubernetes resources or resources bound with other `APIBindings`.
436+
437+
!!! information
438+
When an `APIExport` is changed after workspaces have bound to it, new or changed APIs are automatically propagated
439+
to all `APIBindings`. New permission claims on the other hand are NOT automatically accepted.
425440

426441
Returning to our example, we can grant the requested permissions in the `APIBinding`:
427442

@@ -450,7 +465,8 @@ spec:
450465
matchAll: true
451466
```
452467

453-
It should be noted that `APIBindings` do not create `CRDs` or `APIResourceSchemas`in the workspace. Instead APIs are directly bound using Kubernetes' internal binding mechanism behind the scenes.
468+
It should be noted that `APIBindings` do not create `CRDs` or `APIResourceSchemas` in the workspace.
469+
Instead APIs are directly bound using Kubernetes' internal binding mechanism behind the scenes.
454470

455471
##### Verbs
456472

@@ -459,10 +475,51 @@ the verbs in the APIBinding and the verbs in the appropriate APIExport.
459475

460476
##### Selector
461477

462-
`APIBindings` allow API consumers to scope an API provider's access to claimed resources via the `selector` field on a permission claim. This means that providers will only be able to see and access those objects matched by the `selector`.
478+
`APIBindings` allow API consumers to scope an API provider's access to claimed resources via the `selector` field on
479+
a permission claim. This means that providers will only be able to see and access those objects matched by
480+
the `selector`.
481+
482+
There are two types of selectors at the moment:
483+
484+
- `matchAll`: gives the service provider access to all objects of a claimed resource
485+
- label selector: gives the service provider access only to objects which are satisfying the given label selector
486+
487+
The `matchAll` selector is shown in the example above.
488+
489+
A label selector can be defined using `matchLabels` or `matchExpressions`:
490+
491+
- `matchLabels` specifies a set of labels (key-value pairs). For the selector to match, **all** of the listed labels
492+
must be present on the object.
493+
- `matchExpressions` specifies a set of expressions that are evaluated against object’s labels. If multiple expressions
494+
are specified, **all must evaluate to `true`** for the selector to match.
495+
496+
```yaml
497+
...
498+
permissionClaims:
499+
- resource: configmaps
500+
verbs: ["get", "list", "create"]
501+
state: Accepted
502+
selector:
503+
matchLabels:
504+
env: prod
505+
app: logbook
506+
- resource: things
507+
group: somegroup.kcp.io
508+
identityHash: 5fdf7c7aaf407fd1594566869803f565bb84d22156cef5c445d2ee13ac2cfca6
509+
verbs: ["*"]
510+
state: Accepted
511+
selector:
512+
matchExpressions:
513+
- key: env
514+
operator: In
515+
values: ["prod", "staging"]
516+
```
463517

464518
!!! information
465-
Currently, only `selector.matchAll=true` is supported, giving the provider that owns the `APIExport` full access to all objects of a claimed resource. Additional selectors are planned for upcoming releases.
519+
Special attention is needed by the service provider when creating or updating an object via the APIExport Virtual
520+
Workspace. If `matchLabels` is used, the specified labels will be automatically applied to the object that's being
521+
applied even if not specified by the service provider. However, that's not the case for `matchExpressions`,
522+
in which case the service provider needs to explicitly specify labels upon applying the object.
466523

467524
---
468525

docs/content/concepts/quickstart-tenancy-and-apis.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ spec:
365365

366366
Operations allowed on the resources for which permission claim is accepted is defined as the intersection of the verbs in the `APIBinding` and the verbs in the `APIExport`. Verbs in this case are matching the verbs used by the [Kubernetes API](https://kubernetes.io/docs/reference/using-api/api-concepts/#api-verbs). There is the possibility to further limit the access claim to single resources.
367367

368+
PermissionClaims allows for additional selectors, for more details, check out the [APIBindings documentation](./apis/exporting-apis.md#apibinding).
369+
368370
## Dig Deeper into APIExports
369371

370372
Switching back to the service provider persona:

pkg/permissionclaim/permissionclaim_labeler.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222

2323
authenticationv1 "k8s.io/api/authentication/v1"
2424
authorizationv1 "k8s.io/api/authorization/v1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
klabels "k8s.io/apimachinery/pkg/labels"
2527
"k8s.io/apimachinery/pkg/runtime/schema"
2628
"k8s.io/client-go/tools/cache"
2729
"k8s.io/klog/v2"
@@ -117,10 +119,23 @@ func (l *Labeler) LabelsFor(ctx context.Context, cluster logicalcluster.Name, gr
117119
}
118120

119121
for _, claim := range binding.Spec.PermissionClaims {
120-
if claim.State != apisv1alpha2.ClaimAccepted || claim.Group != normalizedGR.Group || claim.Resource != normalizedGR.Resource || !claim.Selector.MatchAll {
122+
if claim.State != apisv1alpha2.ClaimAccepted || claim.Group != normalizedGR.Group || claim.Resource != normalizedGR.Resource {
121123
continue
122124
}
123125

126+
if !claim.Selector.MatchAll {
127+
selector, err := metav1.LabelSelectorAsSelector(&claim.Selector.LabelSelector)
128+
if err != nil {
129+
logger.Error(err, "error calculating permission claim label key and value",
130+
"claim", claim.String())
131+
continue
132+
}
133+
134+
if !selector.Matches(klabels.Set(resourceLabels)) {
135+
continue
136+
}
137+
}
138+
124139
k, v, err := permissionclaims.ToLabelKeyAndValue(logicalcluster.From(export), export.Name, claim.PermissionClaim)
125140
if err != nil {
126141
// extremely unlikely to get an error here - it means the json marshaling failed

pkg/reconciler/apis/permissionclaimlabel/permissionclaimlabel_resource_reconcile.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import (
3434

3535
"github.com/kcp-dev/logicalcluster/v3"
3636

37-
apisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
37+
apisv1alpha2 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha2"
3838
)
3939

4040
func (c *resourceController) reconcile(ctx context.Context, obj *unstructured.Unstructured, gvr *schema.GroupVersionResource) error {
@@ -48,7 +48,7 @@ func (c *resourceController) reconcile(ctx context.Context, obj *unstructured.Un
4848

4949
actualClaimLabels := make(map[string]string)
5050
for k, v := range obj.GetLabels() {
51-
if strings.HasPrefix(k, apisv1alpha1.APIExportPermissionClaimLabelPrefix) {
51+
if strings.HasPrefix(k, apisv1alpha2.APIExportPermissionClaimLabelPrefix) {
5252
actualClaimLabels[k] = v
5353
}
5454
}

pkg/server/virtual.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ func newVirtualConfig(
8585
return nil, err
8686
}
8787

88+
admissionOptions := virtualoptions.NewAdmission()
89+
if err := admissionOptions.ApplyTo(&recommendedConfig.Config, func() []virtualrootapiserver.NamedVirtualWorkspace {
90+
return c.Extra.VirtualWorkspaces
91+
}); err != nil {
92+
return nil, err
93+
}
94+
8895
c.Extra.VirtualWorkspaces, err = o.Virtual.VirtualWorkspaces.NewVirtualWorkspaces(
8996
config,
9097
virtualcommandoptions.DefaultRootPathPrefix,

0 commit comments

Comments
 (0)