From fed0d806f9272032ac081041100d28522a244fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= <26610571+oliverbaehler@users.noreply.github.com> Date: Thu, 28 May 2026 13:57:17 +0200 Subject: [PATCH 1/9] feat: add migration clarification (#88) * feat: add release additions Signed-off-by: Oliver Baehler * feat: update api references Signed-off-by: Oliver Baehler * feat: update openshfit install docs (#87) * feat: update openshfit install docs Signed-off-by: sandert-k8s * fix: add red hat exclude in lychee Signed-off-by: sandert-k8s --------- Signed-off-by: sandert-k8s Signed-off-by: Oliver Baehler * feat: add migration clarification Signed-off-by: Oliver Baehler * fix: broken links Signed-off-by: Oliver Baehler * fix: broken links Signed-off-by: Oliver Baehler --------- Signed-off-by: Oliver Baehler Signed-off-by: sandert-k8s Co-authored-by: Sander Tervoert <32864332+sandert-k8s@users.noreply.github.com> Signed-off-by: Oliver Baehler --- content/en/docs/operating/setup/installation.md | 4 ++-- content/en/docs/tenants/enforcement.md | 6 +++--- content/en/ecosystem/integrations/teleport.md | 7 +++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/content/en/docs/operating/setup/installation.md b/content/en/docs/operating/setup/installation.md index c363ac9..c677253 100644 --- a/content/en/docs/operating/setup/installation.md +++ b/content/en/docs/operating/setup/installation.md @@ -118,8 +118,8 @@ manager: Before you enable this option, you must implement the required permissions for your use case. Depending on which features you are using, you may need to take manual action, for example: * [Migrate additional RoleBindings](/docs/tenants/permissions/#strict) - - +* [Migrate `TenantResources` to use impersonation](/docs/replications/tenant/#impersonation) +* [Migrate `GlobalTenantResources` to use impersonation](/docs/replications/global/#impersonation) ### Admission Policies diff --git a/content/en/docs/tenants/enforcement.md b/content/en/docs/tenants/enforcement.md index 2c92513..b696c6f 100644 --- a/content/en/docs/tenants/enforcement.md +++ b/content/en/docs/tenants/enforcement.md @@ -516,7 +516,7 @@ With the above configuration, any attempt of Alice to create a `Service` of type > Note: This feature is offered only by API type `GatewayClass` in group `gateway.networking.k8s.io` version `v1`. -[GatewayClass](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayClass) is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of `Gateways` that can be instantiated. [Read More](https://gateway-api.sigs.k8s.io/api-types/gatewayclass/) +[GatewayClass](https://gateway-api.sigs.k8s.io/docs/concepts/api-overview/#gatewayclass) is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of `Gateways` that can be instantiated. [Read More](https://gateway-api.sigs.k8s.io/docs/concepts/api-overview/#gatewayclass) Bill can assign a set of dedicated `GatewayClasses` to the `solar` `Tenant` to force the applications in the `solar` `Tenant` to be published only by the assigned Gateway Controller: @@ -535,7 +535,7 @@ spec: env: "production" ``` -With the said Tenant specification, Alice can create a [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/) resource if `spec.gatewayClassName` equals to: +With the said Tenant specification, Alice can create a [Gateway](https://gateway-api.sigs.k8s.io/docs/concepts/api-overview/#gateway) resource if `spec.gatewayClassName` equals to: * Any `GatewayClass` which has the label `env` with the value `production` @@ -563,7 +563,7 @@ Any attempt of Alice to use a non-valid `GatewayClass`, or missing it, is denied > Note: The Default `GatewayClass` must have a label which is allowed within the tenant. This behavior is only implemented this way for the `GatewayClass` default. -This feature allows specifying a custom default value on a `Tenant` basis. Currently there is no global default feature for a `GatewayClass`. Each [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/) must have a `spec.gatewayClassName` set. +This feature allows specifying a custom default value on a `Tenant` basis. Currently there is no global default feature for a `GatewayClass`. Each [Gateway](https://gateway-api.sigs.k8s.io/docs/concepts/api-overview/#gateway) must have a `spec.gatewayClassName` set. ```yaml apiVersion: capsule.clastix.io/v1beta2 diff --git a/content/en/ecosystem/integrations/teleport.md b/content/en/ecosystem/integrations/teleport.md index 3a84d07..47f2892 100644 --- a/content/en/ecosystem/integrations/teleport.md +++ b/content/en/ecosystem/integrations/teleport.md @@ -15,7 +15,7 @@ If you want to pass requests from teleport users through the capsule-proxy for u ## Prerequisites -1. [Capsule](/docs/operating/setup/installation/) +1. [Capsule](/docs/proxy/setup/installation/) 2. [Capsule Proxy](/docs/proxy/) 3. [Teleport Cluster](https://goteleport.com/) 4. [teleport-kube-agent](https://goteleport.com/docs/enroll-resources/kubernetes-access/getting-started/) @@ -64,8 +64,7 @@ If you want to test this integration locally, follow these steps. ### References - -- -- +- [Proxy Installation](/docs/proxy/setup/installation/) ### Tools @@ -280,7 +279,7 @@ extraVolumeMounts: - `kubectl create ns foo-bar` (should fail, since not owner) - `kubectl create ns oil-bar` (should succeed) -From here you could enable `ProxyClusterScoped` [feature gate](https://projectcapsule.dev/docs/proxy/options/) to allow listing of cluster scoped resources via [ProxySettings](https://projectcapsule.dev/docs/proxy/proxysettings/). +From here you could enable `ProxyClusterScoped` [feature gate](/docs/proxy/setup/options/) to allow listing of cluster scoped resources via [ProxySettings](/docs/proxy/proxysettings/). ## Cleanup From b6426b492226c7cdc57e4186b828e96739b0111a Mon Sep 17 00:00:00 2001 From: Oliver Baehler Date: Fri, 29 May 2026 13:46:40 +0200 Subject: [PATCH 2/9] feat: add proxy args Signed-off-by: Oliver Baehler --- content/en/docs/proxy/setup/options.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/content/en/docs/proxy/setup/options.md b/content/en/docs/proxy/setup/options.md index 682b918..698f22a 100644 --- a/content/en/docs/proxy/setup/options.md +++ b/content/en/docs/proxy/setup/options.md @@ -32,6 +32,10 @@ options: capsuleConfigurationName: default # -- Define which groups must be ignored while proxying requests ignoredUserGroups: [] + # -- Names of the groups which are not used for impersonation (considered after impersonation-group-regexp) + ignoredImpersonationGroups: [] + # -- Regular expression to match the groups which are considered for impersonation + impersonationGroupRegexp: "" # -- Specify if capsule-proxy will use SSL oidcUsernameClaim: preferred_username # -- Specify if capsule-proxy will use SSL @@ -54,7 +58,7 @@ options: disableCaching: false # -- Enable the rolebinding reflector, which allows to list the namespaces, where a rolebinding mentions a user. roleBindingReflector: false - # -- Authentication types to be used for requests. Possible Auth Types: [BearerToken, TLSCertificate] + # -- Authentication types to be used for requests. Possible Auth Types: [BearerToken, TLSCertificate, XForwardedClientCert] authPreferredTypes: "BearerToken,TLSCertificate" # -- QPS to use for interacting with Kubernetes API Server. clientConnectionQPS: 20 @@ -62,6 +66,9 @@ options: clientConnectionBurst: 30 # -- Enable Pprof for profiling pprof: false + # -- CIDR ranges of trusted proxies allowed to make requests to the proxy + trustedProxyCidrs: [] + ``` The following options are available for the Capsule Proxy Controller: From 09e6191d40378567b6d6920691de14dd5b447fc8 Mon Sep 17 00:00:00 2001 From: Oliver Baehler Date: Mon, 8 Jun 2026 10:29:22 +0200 Subject: [PATCH 3/9] feat: clarify matching strategies Signed-off-by: Oliver Baehler --- .../customquotas/_index.md | 105 +++++++++++++++++- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/content/en/docs/resource-management/customquotas/_index.md b/content/en/docs/resource-management/customquotas/_index.md index 3b2f349..b50b99e 100644 --- a/content/en/docs/resource-management/customquotas/_index.md +++ b/content/en/docs/resource-management/customquotas/_index.md @@ -129,6 +129,104 @@ The following constraints apply to the JSONPath: * Values can resolve to array results, which are then summed up. (For example, `.spec.containers[*].resources.limits.cpu` would sum the CPU limits of all containers in a Pod.) * Missing fields are resulting in an error, as it's assumed that if a path requires calculation it should force the targeted sources to define these paths. Meaning if you eg define this JP `.spec.initContainers[*].resources.limits.cpu` on a Pod that has no initContainers, it will error. If you want to only calculate the path if it exists, you can use a [fielselector](#fieldselectors) to only match objects where the path exists, for example with `.spec.initContainers` as fieldSelector. +#### Matching Strategies + +Implemententations how JSONPath expressions are evaluated and how their results are interpreted for conditional matching. + +#### Truthy + +When a `fieldSelectors` entry does not contain a top-level `=` or `==`, Capsule treats it as a JSONPath expression. + +The selector matches when the JSONPath result is truthy. + +Truthy evaluation rules: + +* empty result: false +* `false`, case-insensitive: false +* `0`: false +* any other non-empty result: true + +Example: + +```yaml +spec: + sources: + - apiVersion: v1 + kind: PersistentVolumeClaim + op: add + path: .spec.resources.requests.storage + selectors: + - fieldSelectors: + - '.spec.accessModes[?(@=="ReadWriteOnce")]' + - '.status.phase' +``` + +This selector matches only if: + +* `.spec.accessModes[?(@=="ReadWriteOnce")]` returns a non-empty result +* `.status.phase returns a non-empty result` + +For example, this matches a PVC with: + +```yaml +spec: + accessModes: + - ReadWriteOnce +status: + phase: Bound +``` + +#### Equality + +When an entry contains a top-level `=` or `==` (not nested JP expressions), Capsule treats it as an equality comparison. The left side is evaluated as a JSONPath expression. The right side is compared as a **string**. + +```yaml +spec: + sources: + - apiVersion: v1 + kind: Service + op: count + selectors: + - fieldSelectors: + - '.spec.type=ClusterIP' +``` + +The following forms are equivalent: + +```yaml +fieldSelectors: + - '.spec.type=ClusterIP' + - '.spec.type==ClusterIP' + - '.spec.type=="ClusterIP"' + - ".spec.type=='ClusterIP'" +``` + +A `==` inside a JSONPath filter is still treated as part of the JSONPath expression, not as Capsule equals matching. + +For example: + +```yaml +fieldSelectors: + - '.spec.accessModes[?(@=="ReadWriteOnce")]' +``` + +This is interpreted as a truthy JSONPath selector, not as an equals selector. + + +**Use JSONPath filters for arrays:** + +```yaml +fieldSelectors: + - '.spec.accessModes[?(@=="ReadWriteOnce")]' +``` + +**Use equals matching for scalar fields:** + +```yaml +fieldSelectors: + - '.spec.type=ClusterIP' +``` + ### Quota Matches As it's the case with native [ResourceQuotas](https://kubernetes.io/docs/concepts/policy/resource-quotas/#how-resource-quota-works), when a request is made, Capsule evaluates all existing CustomQuotas and GlobalCustomQuotas to determine which ones match the request. Always the smallest quantity of quotas is enforced, meaning that if multiple quotas match a request, the one with the least available capacity will be the one that determines whether the request is allowed or denied. @@ -366,12 +464,7 @@ spec: fieldSelectors are additional per-source filters. Each entry is a JSONPath expression evaluated against the candidate object. -A selector entry matches when its JSONPath result is truthy: - - * empty result, `false` or `0`: false - * any other non-empty result: true - -Given: +[View the available matching semantics](#matching-strategies) for fieldSelectors. ```yaml spec: From 2d7b04f2d50aaf6aaea5dce3c8cbd8587c79f8a0 Mon Sep 17 00:00:00 2001 From: Sander Tervoert <32864332+sandert-k8s@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:28:41 +0200 Subject: [PATCH 4/9] chore: housekeeping (#91) * chore: add status objects in apireference Signed-off-by: sandert-k8s * chore: typo proxysetting Signed-off-by: sandert-k8s * chore: fix spelling Signed-off-by: sandert-k8s --------- Signed-off-by: sandert-k8s Signed-off-by: Oliver Baehler --- .../namespace-migration-across-tenants.md | 8 +- content/en/docs/guides/use-fluxcd.md | 28 +-- content/en/docs/operating/architecture.md | 20 +- content/en/docs/operating/authentication.md | 8 +- content/en/docs/operating/backup-restore.md | 8 +- .../operating/best-practices/networking.md | 2 +- .../operating/best-practices/workloads.md | 10 +- .../en/docs/operating/setup/configuration.md | 30 +-- content/en/docs/overview/benchmark.md | 196 +++++++++--------- content/en/docs/proxy/proxysettings.md | 7 +- content/en/docs/proxy/reference.md | 62 ++++++ content/en/docs/proxy/setup/installation.md | 8 +- content/en/docs/reference.md | 65 +++++- .../resourcepools/_index.md | 8 +- content/en/docs/tenants/administration.md | 2 +- content/en/docs/tenants/permissions.md | 6 +- content/en/docs/tenants/quickstart.md | 2 +- content/en/ecosystem/_index.md | 2 +- content/en/ecosystem/integrations/kyverno.md | 6 +- content/en/ecosystem/integrations/lens.md | 6 +- .../en/ecosystem/integrations/monitoring.md | 2 +- content/en/ecosystem/integrations/tekton.md | 18 +- content/en/ecosystem/integrations/teleport.md | 2 +- .../en/project/contributions/guidelines.md | 8 +- 24 files changed, 317 insertions(+), 197 deletions(-) diff --git a/content/en/docs/guides/namespace-migration-across-tenants.md b/content/en/docs/guides/namespace-migration-across-tenants.md index f6c75fa..acf6970 100644 --- a/content/en/docs/guides/namespace-migration-across-tenants.md +++ b/content/en/docs/guides/namespace-migration-across-tenants.md @@ -3,9 +3,9 @@ title: Namespace Migration Across Tenants weight: 2 description: "A Step-by-Step Guide to Namespace Migration" --- -Capsule relays on two components to associate given namespace with tenant. -- Namespace's OwnerReference.name pointing to the Tenant defintion -- Namespace's OwnerReference.uid pointing to the Tenant defintion +Capsule relies on two components to associate given namespace with tenant. +- Namespace's OwnerReference.name pointing to the Tenant definition +- Namespace's OwnerReference.uid pointing to the Tenant definition If a cluster administrator changes the Namespace by matching the other Tenant with the proper UID and name, the Namespace can be easily transferred. @@ -26,7 +26,7 @@ kubectl get tnt wind -o jsonpath='{.metadata.uid}' ``` While altering ownerReferences name is sufficient on its own, it's highly recommended to edit the UID to match the output of the previous commands. ```bash -kubectl edit ns ns-foo +kubectl edit ns ns-foo ``` If everything is set correctly, the namespace will be correctly recognized as part of the new tenant. ```bash diff --git a/content/en/docs/guides/use-fluxcd.md b/content/en/docs/guides/use-fluxcd.md index 02e2d6a..a6b70cd 100644 --- a/content/en/docs/guides/use-fluxcd.md +++ b/content/en/docs/guides/use-fluxcd.md @@ -8,7 +8,7 @@ description: How to operate Tenants the GitOps way with Flux and Capsule togethe This document will guide you to manage Tenant resources the GitOps way with Flux configured with the [multi-tenancy lockdown](https://fluxcd.io/docs/installation/#multi-tenancy-lockdown). -The proposed approach consists on making Flux to reconcile Tenant resources as Tenant Owners, while still providing Namespace as a Service to Tenants. +The proposed approach consists of making Flux reconcile Tenant resources as Tenant Owners, while still providing Namespace as a Service to Tenants. This means that Tenants can operate and declare multiple Namespaces in their own Git repositories while not escaping the policies enforced by Capsule. @@ -80,7 +80,7 @@ The addon will automate: * RBAC configuration for the `Tenant` owner `ServiceAccount` * `Tenant` owner `ServiceAccount` token generation * `Tenant` owner `kubeconfig` needed to send Flux reconciliation requests through the Capsule proxy -* `Tenant` `kubeconfig` distribution accross all Tenant `Namespace`s. +* `Tenant` `kubeconfig` distribution across all Tenant `Namespace`s. The last automation is needed so that the `kubeconfig` can be set on `Kustomization`s/`HelmRelease`s across all `Tenant`'s `Namespace`s. @@ -127,7 +127,7 @@ Let's analyze the setup field by field: The *oil* tenant can also declare new `Namespace`s thanks to the segregation provided by Capsule. -> Note: it can be avoided to explicitely set the the service account name when it's set as default Service Account name at Flux's [kustomize-controller level](https://fluxcd.io/flux/installation/configuration/multitenancy/#how-to-configure-flux-multi-tenancy) via the `default-service-account` flag. +> Note: it can be avoided to explicitly set the service account name when it's set as default Service Account name at Flux's [kustomize-controller level](https://fluxcd.io/flux/installation/configuration/multitenancy/#how-to-configure-flux-multi-tenancy) via the `default-service-account` flag. More information are available in the [addon repository](https://github.com/projectcapsule/capsule-addon-fluxcd). @@ -183,7 +183,7 @@ What if we would like to provide tenants the ability to manage also their own sp ## Manual setup -> Legenda: +> Legend: > - Privileged space: group of Namespaces which are not part of any Tenant. > - Privileged identity: identity that won't pass through Capsule tenant access control. > - Unprivileged space: group of Namespaces which are part of a Tenant. @@ -261,7 +261,7 @@ patches: name: "(kustomize-controller|helm-controller)" ``` -This way tenants can't make Flux apply their Reconciliation resources with Flux's privileged Service Accounts, by not specifying a `spec.ServiceAccountName` on them. +This way tenants can't make Flux apply their Reconciliation resources with Flux's privileged Service Accounts, by not specifying a `spec.ServiceAccountName` on them. At the same time at resource-level in privileged space we still can specify a privileged ServiceAccount, and its reconciliation requests won't pass through Capsule validation: @@ -282,7 +282,7 @@ spec: #### Kubeconfig We also need to specify on Tenant's Reconciliation resources, the `Secret` with **`kubeconfig`** configured to use the **Capsule Proxy** as the API server in order to provide the Tenant GitOps Reconciler the ability to list cluster-level resources. -The `kubeconfig` would specify also as the token the Tenant GitOps Reconciler SA token, +The `kubeconfig` would specify also as the token the Tenant GitOps Reconciler SA token, For example: @@ -296,7 +296,7 @@ spec: kubeConfig: secretRef: name: gitops-reconciler-kubeconfig - key: kubeconfig + key: kubeconfig sourceRef: kind: GitRepository name: my-tenant @@ -323,14 +323,14 @@ patches: - patch: | - op: add path: /spec/template/spec/containers/0/args/0 - value: --no-cross-namespace-refs=true + value: --no-cross-namespace-refs=true target: kind: Deployment name: "(kustomize-controller|helm-controller|notification-controller|image-reflector-controller|image-automation-controller)" - patch: | - op: add path: /spec/template/spec/containers/0/args/- - value: --no-remote-bases=true + value: --no-remote-bases=true target: kind: Deployment name: "kustomize-controller" @@ -344,7 +344,7 @@ patches: - patch: | - op: add path: /spec/serviceAccountName - value: kustomize-controller + value: kustomize-controller target: kind: Kustomization name: "flux-system" @@ -443,7 +443,7 @@ this is the required set of resources to setup a Tenant: ``` - `Secret` with `kubeconfig` for the Tenant GitOps Reconciler with Capsule Proxy as `kubeconfig.server` and the SA token as `kubeconfig.token`. > This is supported only with Service Account static tokens. -- Flux Source and Reconciliation resources that refer to Tenant desired state. This typically points to a specific path inside a dedicated Git repository, where tenant's root configuration reside: +- Flux Source and Reconciliation resources that refer to Tenant desired state. This typically points to a specific path inside a dedicated Git repository, where tenant's root configuration reside: ```yaml apiVersion: source.toolkit.fluxcd.io/v1beta2 kind: GitRepository @@ -505,7 +505,7 @@ spec: kubeConfig: secretRef: name: gitops-reconciler-kubeconfig - key: kubeconfig + key: kubeconfig sourceRef: kind: GitRepository name: my-tenant @@ -547,7 +547,7 @@ Furthermore, let's see if there are other vulnerabilities we are able to protect #### Impersonate privileged SA -Then, what if a tenant tries to escalate by using one of the Flux controllers privileged `ServiceAccount`s? +Then, what if a tenant tries to escalate by using one of the Flux controllers privileged `ServiceAccount`s? As `spec.ServiceAccountName` for Reconciliation resource cannot cross-namespace reference Service Accounts, tenants are able to let Flux apply his own resources only with ServiceAccounts that reside in his own Namespaces. Which is, Namespace of the ServiceAccount and Namespace of the Reconciliation resource must match. @@ -572,5 +572,5 @@ For other protections against threats in this multi-tenancy scenario please see - https://fluxcd.io/docs/installation/#multi-tenancy-lockdown - https://fluxcd.io/blog/2022/05/may-2022-security-announcement/ - https://github.com/projectcapsule/capsule-proxy/issues/218 -- https://github.com/projectcapsule/capsule/issues/528 +- https://github.com/projectcapsule/capsule/issues/528 - https://fluxcd.io/docs/guides/repository-structure/ diff --git a/content/en/docs/operating/architecture.md b/content/en/docs/operating/architecture.md index bd33dfc..4ded795 100644 --- a/content/en/docs/operating/architecture.md +++ b/content/en/docs/operating/architecture.md @@ -12,16 +12,16 @@ Introducing a new separation of duties can lead to a significant paradigm shift. The answer to this question may be influenced by the following aspects: -* **Are the Cluster Adminsitrators willing to grant permissions to Tenant Owners**? - * _You might have a problem with know-how and probably your organisation is not yet pushing Kubernetes itself enough as a key strategic plattform. The key here is enabling Plattform Users through good UX and know-how transfers_ +* **Are the Cluster Administrators willing to grant permissions to Tenant Owners?** + * _You might have a problem with know-how and probably your organisation is not yet pushing Kubernetes itself enough as a key strategic platform. The key here is enabling Platform Users through good UX and know-how transfers_ -* **Who is responsible for the deployed workloads within the Tenants?**? +* **Who is responsible for the deployed workloads within the Tenants?** * _If Platform Administrators are still handling this, a true “shift left” has not yet been achieved._ -* **Who gets paged during a production outage within a Tenant’s application?**? +* **Who gets paged during a production outage within a Tenant’s application?** * _You’ll need robust monitoring that enables Tenant Owners to clearly understand and manage what’s happening inside their own tenant._ -* **Are your customers technically capable of working directly with the Kubernetes API?**? +* **Are your customers technically capable of working directly with the Kubernetes API?** * _If not, you may need to build a more user-friendly platform with better UX — for example, a multi-tenant ArgoCD setup, or UI layers like Headlamp._ ## Personas @@ -93,11 +93,11 @@ Capsule provides robust tools to strictly enforce tenant boundaries, ensuring th ## Layouts -Let's dicuss different Tenant Layouts which could be used . These are just approaches we have seen, however you might also find a combination of these which fits your use-case. +Let's discuss different Tenant Layouts which could be used . These are just approaches we have seen, however you might also find a combination of these which fits your use-case. ### Tenant As A Service -With this approach you essentially just provide your Customers with the Tenant on your cluster. The rest is their responsability. This concludes to a shared responsibility model. This can be achieved when also the Tenant Owners are responsible for everything they are provisiong within their Tenant's namespaces. +With this approach you essentially just provide your Customers with the Tenant on your cluster. The rest is their responsibility. This concludes to a shared responsibility model. This can be achieved when also the Tenant Owners are responsible for everything they are provisiong within their Tenant's namespaces. ![Resourcepool Dashboard](/images/content/architecture/layout-taas.drawio.png) @@ -119,15 +119,15 @@ Strong tenant isolation, ensuring that any noisy neighbor effects remain confine ### Shared -With this approach you share the nodes amongst all Tenants, therefor giving you more potential for optimizing resources on a node level. It's a common pattern to separate the controllers needed to power your Distribution (operators) form the actual workload. This ensures smooth operations for the cluster +With this approach you share the nodes amongst all Tenants, therefore giving you more potential for optimizing resources on a node level. It's a common pattern to separate the controllers needed to power your Distribution (operators) from the actual workload. This ensures smooth operations for the cluster **Overview**: - ✅ Designed for cost efficiency . -- ✅ Suitable for applications that typically experience low resource fluctuations and run with multiple replicas. +- ✅ Suitable for applications that typically experience low resource fluctuations and run with multiple replicas. - ❌ Not ideal for applications that are not cloud-native ready, as they may adversely affect the operation of other applications or the maintenance of node pools. - ❌ Not ideal if strong isolation is required - + ![Shared Nodepool](/images/content/scheduling-shared.drawio.png) diff --git a/content/en/docs/operating/authentication.md b/content/en/docs/operating/authentication.md index 8b3ae24..b2973c8 100644 --- a/content/en/docs/operating/authentication.md +++ b/content/en/docs/operating/authentication.md @@ -41,7 +41,7 @@ $ curl -k -s https://${OIDC_ISSUER}/protocol/openid-connect/token \ The result will include an `ACCESS_TOKEN`, a `REFRESH_TOKEN`, and an `ID_TOKEN`. The access-token can generally be disregarded for Kubernetes. It would be used if the identity provider was managing roles and permissions for the users but that is done in Kubernetes itself with RBAC. The id-token is short lived while the refresh-token has longer expiration. The refresh-token is used to fetch a new id-token when the id-token expires. ```json -{ +{ "access_token":"ACCESS_TOKEN", "refresh_token":"REFRESH_TOKEN", "id_token": "ID_TOKEN", @@ -85,7 +85,7 @@ Configuring Kubernetes for OIDC Authentication requires adding several parameter #### Authentication Configuration (Recommended) -The configuration file approach allows you to configure multiple JWT authenticators, each with a unique issuer.url and issuer.discoveryURL. The configuration file even allows you to specify CEL expressions to map claims to user attributes, and to validate claims and user information. +The configuration file approach allows you to configure multiple JWT authenticators, each with a unique issuer.url and issuer.discoveryURL. The configuration file even allows you to specify CEL expressions to map claims to user attributes, and to validate claims and user information. ```yaml apiVersion: apiserver.config.k8s.io/v1beta1 @@ -168,7 +168,7 @@ There are two options to use kubectl with OIDC: **Plugin** -One way to use OIDC authentication is the use of a kubectl plugin. The [Kubelogin Plugin](https://github.com/int128/kubelogin) for kubectl simplifies the process of obtaining an OIDC token and configuring kubectl to use it. Follow the link to obtain installation instructions. +One way to use OIDC authentication is the use of a kubectl plugin. The [Kubelogin Plugin](https://github.com/int128/kubelogin) for kubectl simplifies the process of obtaining an OIDC token and configuring kubectl to use it. Follow the link to obtain installation instructions. ```shell kubectl oidc-login setup \ @@ -203,7 +203,7 @@ Point the kubectl to the URL where the Kubernetes APIs Server is reachable: ```shell $ kubectl config set-cluster mycluster \ - --server=https://kube.projectcapulse.io:6443 \ + --server=https://kube.projectcapsule.io:6443 \ --certificate-authority=~/.kube/ca.crt ``` diff --git a/content/en/docs/operating/backup-restore.md b/content/en/docs/operating/backup-restore.md index d85bea0..b031ea3 100644 --- a/content/en/docs/operating/backup-restore.md +++ b/content/en/docs/operating/backup-restore.md @@ -10,7 +10,7 @@ When coming to backup and restore in Kubernetes, we have two main requirements: * Configurations backup * Data backup - + The first requirement aims to backup all the resources stored into etcd database, for example: namespaces, pods, services, deployments, etc. The second is about how to backup stateful application data as volumes. The main limitation of Velero is the multi tenancy. Currently, Velero does not support multi tenancy meaning it can be only used from admin users and so it cannot provided "as a service" to the users. This means that the cluster admin needs to take care of users' backup. @@ -73,7 +73,7 @@ velero create backup solar-namespaces \ --include-namespaces solar-production,solar-development,solar-marketing ``` -resulting to the following Velero object: +resulting in the following Velero object: ```yaml apiVersion: velero.io/v1 @@ -93,7 +93,7 @@ spec: ttl: 720h0m0s ``` -> Velero requires an Object Storage backend where to store backups, you should take care of this requirement before to use Velero. +> Velero requires an Object Storage backend where to store backups, you should take care of this requirement before using Velero. ## Restore a tenant from the backup @@ -128,4 +128,4 @@ NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE gas active 9 5 {"pool":"gas"} 44m solar active 9 8 {"pool":"oil"} 43m oil active 9 3 # <<< {"pool":"solar"} 12s -``` \ No newline at end of file +``` diff --git a/content/en/docs/operating/best-practices/networking.md b/content/en/docs/operating/best-practices/networking.md index 0855361..6f38d02 100644 --- a/content/en/docs/operating/best-practices/networking.md +++ b/content/en/docs/operating/best-practices/networking.md @@ -68,7 +68,7 @@ spec: ### Deny Namespace Metadata -In the above example we allow traffic from namespaces with the label `company.com/system: "true"`. This is meant for Kubernetes Operators to eg. scrape the workloads within a tenant. However without further enforcement any namespace can set this label and therefor gain access to any tenant namespace. To prevent this, we must restrict, who can declare this label on namespaces. +In the above example we allow traffic from namespaces with the label `company.com/system: "true"`. This is meant for Kubernetes Operators to eg. scrape the workloads within a tenant. However without further enforcement any namespace can set this label and therefore gain access to any tenant namespace. To prevent this, we must restrict, who can declare this label on namespaces. We can deny such labels on tenant basis. So in this scenario every tenant should disallow the use of these labels on namespaces: diff --git a/content/en/docs/operating/best-practices/workloads.md b/content/en/docs/operating/best-practices/workloads.md index 1492a52..7f29c17 100644 --- a/content/en/docs/operating/best-practices/workloads.md +++ b/content/en/docs/operating/best-practices/workloads.md @@ -44,7 +44,7 @@ metadata: policies.kyverno.io/subject: Pod,User Namespace kyverno.io/kubernetes-version: "1.31" policies.kyverno.io/description: >- - Do not use the host's user namespace. A new userns is created for the pod. + Do not use the host's user namespace. A new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the @@ -80,7 +80,7 @@ In Kubernetes, by default, workloads run with administrative access, which might Many of these concerns were addressed initially by [PodSecurityPolicies](https://kubernetes.io/docs/concepts/security/pod-security-policy) which have been present in the Kubernetes APIs since the very early days. -The Pod Security Policies are deprecated in Kubernetes 1.21 and removed entirely in 1.25. As replacement, the [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) and [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) has been introduced. Capsule support the new standard for tenants under its control as well as the oldest approach. +The Pod Security Policies are deprecated in Kubernetes 1.21 and removed entirely in 1.25. As replacement, the [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) and [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) has been introduced. Capsule supports the new standard for tenants under its control as well as the oldest approach. One of the issues with Pod Security Policies is that it is difficult to apply restrictive permissions on a granular level, increasing security risk. Also the Pod Security Policies get applied when the request is submitted and there is no way of applying them to pods that are already running. For these, and other reasons, the Kubernetes community decided to deprecate the Pod Security Policies. @@ -167,7 +167,7 @@ spec: ``` -All namespaces created by the tenant owner, will inherit the Pod Security labels: +All namespaces created by the tenant owner, will inherit the Pod Security labels: ```yaml apiVersion: v1 @@ -217,7 +217,7 @@ pods "nginx" is forbidden: violates PodSecurity "baseline:latest": privileged (container "nginx" must not set securityContext.privileged=true) ``` -If the tenant owner tries to change o delete the above labels, Capsule will reconcile them to the original tenant manifest set by the cluster admin. +If the tenant owner tries to change or delete the above labels, Capsule will reconcile them to the original tenant manifest set by the cluster admin. As additional security measure, the cluster admin can also prevent the tenant owner to make an improper usage of the above labels: @@ -338,4 +338,4 @@ spec: EOF ``` -Since the assigned `PodSecurityPolicy` explicitly disallows privileged containers, the tenant owner will see her request to be rejected by the Pod Security Policy Admission Controller. \ No newline at end of file +Since the assigned `PodSecurityPolicy` explicitly disallows privileged containers, the tenant owner will see her request to be rejected by the Pod Security Policy Admission Controller. diff --git a/content/en/docs/operating/setup/configuration.md b/content/en/docs/operating/setup/configuration.md index c64b0f4..65cbcfc 100644 --- a/content/en/docs/operating/setup/configuration.md +++ b/content/en/docs/operating/setup/configuration.md @@ -5,11 +5,11 @@ description: > Understand the Capsule configuration options and how to use them. --- -The configuration for the capsule controller is done via it's dedicated configration Custom Resource. You can explain the configuration options and how to use them: +The configuration for the capsule controller is done via its dedicated configuration Custom Resource. You can explain the configuration options and how to use them: ## CapsuleConfiguration -The configuration for Capsule is done via it's dedicated configration Custom Resource. You can explain the configuration options and how to use them: +The configuration for Capsule is done via its dedicated configuration Custom Resource. You can explain the configuration options and how to use them: ```shell kubectl explain capsuleConfiguration.spec @@ -17,7 +17,7 @@ kubectl explain capsuleConfiguration.spec ### `administrators` -These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor be ignored by capsule. May also be handy in GitOps scenarios where certain service accounts need to be able to manage namespaces for all tenants. +These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefore be ignored by capsule. May also be handy in GitOps scenarios where certain service accounts need to be able to manage namespaces for all tenants. [Read More](/docs/operating/architecture/#capsule-administrators) @@ -31,7 +31,7 @@ manager: ### `users` -These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor be ignored by capsule. May also be handy in GitOps scenarios where certain service accounts need to be able to manage namespaces for all tenants. +These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefore be ignored by capsule. May also be handy in GitOps scenarios where certain service accounts need to be able to manage namespaces for all tenants. [Read More](/docs/operating/architecture/#capsule-users) @@ -137,7 +137,7 @@ manager: options: rbac: # -- The ClusterRoles applied for Administrators - adminitrationClusterRoles: + administrationClusterRoles: - capsule-namespace-deleter # -- The ClusterRoles applied for ServiceAccounts which had owner Promotion @@ -160,33 +160,33 @@ For Replications by default the controller ServiceAccount is used to perform the manager: options: impersonation: - # Kubernetes API Endpoint to use for the operations + # Kubernetes API Endpoint to use for the operations endpoint: "https://capsule-proxy.capsule-system.svc:8081" - + # Toggles if TLS verification for the endpoint is performed or not skipTlsVerify: false - + # Key in the secret that holds the CA certificate (e.g., "ca.crt") caSecretKey: "ca.crt" - + # Name of the secret containing the CA certificate caSecretName: "capsule-proxy-tls" - + # Namespace where the CA certificate secret is located caSecretNamespace: "capsule-system" - + # Default ServiceAccount for global resources (GlobalTenantResource) [Cluster Scope] # When defined, users are required to use this ServiceAccount anywhere in the cluster - # unless they explicitly provide their own. Once this is set, Capsule will add this ServiceAccount + # unless they explicitly provide their own. Once this is set, Capsule will add this ServiceAccount # for all GlobalTenantResources, if they don't already have a ServiceAccount defined. globalDefaultServiceAccount: "capsule-global-sa" - + # Namespace of the for the ServiceAccount provided by the globalDefaultServiceAccount property globalDefaultServiceAccountNamespace: "tenant-system" - + # Default ServiceAccount for tenant resources (TenantResource) [Namespaced Scope] # When defined, users are required to use this ServiceAccount anywhere in the cluster - # unless they explicitly provide their own. Once this is set, Capsule will add this ServiceAccount + # unless they explicitly provide their own. Once this is set, Capsule will add this ServiceAccount # for all GlobalTenantResources, if they don't already have a ServiceAccount defined. tenantDefaultServiceAccount: "default" ``` diff --git a/content/en/docs/overview/benchmark.md b/content/en/docs/overview/benchmark.md index f4c40d1..b3ca6fe 100644 --- a/content/en/docs/overview/benchmark.md +++ b/content/en/docs/overview/benchmark.md @@ -83,22 +83,22 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, retrieve the networkpolicies resources in the tenant namespace -```bash -kubectl --kubeconfig alice get networkpolicies +```bash +kubectl --kubeconfig alice get networkpolicies NAME POD-SELECTOR AGE capsule-oil-0 7m5s ``` As a tenant, checks for permissions to manage networkpolicy for each verb -```bash +```bash kubectl --kubeconfig alice auth can-i get networkpolicies kubectl --kubeconfig alice auth can-i create networkpolicies kubectl --kubeconfig alice auth can-i update networkpolicies @@ -112,7 +112,7 @@ Each command must return 'yes' **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -150,14 +150,14 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner check for permissions to manage rolebindings for each verb -```bash +```bash kubectl --kubeconfig alice auth can-i get rolebindings kubectl --kubeconfig alice auth can-i create rolebindings kubectl --kubeconfig alice auth can-i update rolebindings @@ -171,7 +171,7 @@ Each command must return 'yes' **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -209,14 +209,14 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, check for permissions to manage roles for each verb -```bash +```bash kubectl --kubeconfig alice auth can-i get roles kubectl --kubeconfig alice auth can-i create roles kubectl --kubeconfig alice auth can-i update roles @@ -230,7 +230,7 @@ Each command must return 'yes' **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -266,12 +266,12 @@ EOF ``` As cluster admin, run the following command to retrieve the list of non-namespaced resources -```bash +```bash kubectl --kubeconfig cluster-admin api-resources --namespaced=false ``` For all non-namespaced resources, and each verb (get, list, create, update, patch, watch, delete, and deletecollection) issue the following command: -```bash +```bash kubectl --kubeconfig alice auth can-i ``` Each command must return `no` @@ -280,7 +280,7 @@ Each command must return `no` It should, but it does not: -```bash +```bash kubectl --kubeconfig alice auth can-i create selfsubjectaccessreviews yes kubectl --kubeconfig alice auth can-i create selfsubjectrulesreviews @@ -291,7 +291,7 @@ yes Any kubernetes user can create `SelfSubjectAccessReview` and `SelfSubjectRulesReviews` to checks whether he/she can act. First, two exceptions are not an issue. -```bash +```bash kubectl --anyuser auth can-i --list Resources Non-Resource URLs Resource Names Verbs selfsubjectaccessreviews.authorization.k8s.io [] [] [create] @@ -327,7 +327,7 @@ Role: Subjects: Kind Name Namespace ---- ---- --------- - Group capsule.clastix.io + Group capsule.clastix.io kubectl describe clusterrole capsule-namespace-provisioner Name: capsule-namespace-provisioner @@ -344,7 +344,7 @@ Capsule controls self-service namespace creation by limiting the number of names **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -402,15 +402,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, retrieve the networkpolicies resources in the tenant namespace -```bash -kubectl --kubeconfig alice get networkpolicies +```bash +kubectl --kubeconfig alice get networkpolicies NAME POD-SELECTOR AGE capsule-oil-0 7m5s capsule-oil-1 7m5s @@ -418,13 +418,13 @@ capsule-oil-1 7m5s As tenant owner try to modify or delete one of the networkpolicies -```bash +```bash kubectl --kubeconfig alice delete networkpolicies capsule-oil-0 ``` You should receive an error message denying the edit/delete request -```bash +```bash Error from server (Forbidden): networkpolicies.networking.k8s.io "capsule-oil-0" is forbidden: User "oil" cannot delete resource "networkpolicies" in API group "networking.k8s.io" in the namespace "oil-production" ``` @@ -439,7 +439,7 @@ metadata: name: hijacking namespace: oil-production spec: - egress: + egress: - to: - ipBlock: cidr: 0.0.0.0/0 @@ -453,7 +453,7 @@ However, due to the additive nature of networkpolicies, the `DENY ALL` policy se As tenant owner list RBAC permissions set by Capsule -```bash +```bash kubectl --kubeconfig alice get rolebindings NAME ROLE AGE capsule-oil-0-admin ClusterRole/admin 11h @@ -462,7 +462,7 @@ capsule-oil-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter As tenant owner, try to change/delete the rolebinding to escalate permissions -```bash +```bash kubectl --kubeconfig alice edit/delete rolebinding capsule-oil-0-admin ``` @@ -500,7 +500,7 @@ EOF **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -556,14 +556,14 @@ EOF As `oil` tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As `gas` tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig joe create ns gas-production kubectl --kubeconfig joe config set-context --current --namespace gas-production ``` @@ -571,8 +571,8 @@ kubectl --kubeconfig joe config set-context --current --namespace gas-production As `oil` tenant owner, try to retrieve the resources in the `gas` tenant namespaces -```bash -kubectl --kubeconfig alice get serviceaccounts --namespace gas-production +```bash +kubectl --kubeconfig alice get serviceaccounts --namespace gas-production ``` You must receive an error message: @@ -584,8 +584,8 @@ User "oil" cannot list resource "serviceaccounts" in API group "" in the namespa As `gas` tenant owner, try to retrieve the resources in the `oil` tenant namespaces -```bash -kubectl --kubeconfig joe get serviceaccounts --namespace oil-production +```bash +kubectl --kubeconfig joe get serviceaccounts --namespace oil-production ``` You must receive an error message: @@ -598,7 +598,7 @@ User "joe" cannot list resource "serviceaccounts" in API group "" in the namespa **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenants oil gas ``` @@ -686,15 +686,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod and see new capabilities cannot be added in the tenant namespaces -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -718,7 +718,7 @@ You must have the pod blocked by PodSecurityPolicy. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -770,14 +770,14 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, check the permissions to modify/delete the quota in the tenant namespace: -```bash +```bash kubectl --kubeconfig alice auth can-i create quota kubectl --kubeconfig alice auth can-i update quota kubectl --kubeconfig alice auth can-i patch quota @@ -790,7 +790,7 @@ Each command must return 'no' **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -864,7 +864,7 @@ EOF As `oil` tenant owner, run the following commands to create a namespace and resources in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production kubectl --kubeconfig alice run webserver --image nginx:latest @@ -873,7 +873,7 @@ kubectl --kubeconfig alice expose pod webserver --port 80 As `gas` tenant owner, run the following commands to create a namespace and resources in the given tenant -```bash +```bash kubectl --kubeconfig joe create ns gas-production kubectl --kubeconfig joe config set-context --current --namespace gas-production kubectl --kubeconfig joe run webserver --image nginx:latest @@ -882,14 +882,14 @@ kubectl --kubeconfig joe expose pod webserver --port 80 As `oil` tenant owner, verify you can access the service in `oil` tenant namespace but not in the `gas` tenant namespace -```bash +```bash kubectl --kubeconfig alice exec webserver -- curl http://webserver.oil-production.svc.cluster.local kubectl --kubeconfig alice exec webserver -- curl http://webserver.gas-production.svc.cluster.local ``` -Viceversa, as `gas` tenant owner, verify you can access the service in `gas` tenant namespace but not in the `oil` tenant namespace +Vice versa, as `gas` tenant owner, verify you can access the service in `gas` tenant namespace but not in the `oil` tenant namespace -```bash +```bash kubectl --kubeconfig alice exec webserver -- curl http://webserver.oil-production.svc.cluster.local kubectl --kubeconfig alice exec webserver -- curl http://webserver.gas-production.svc.cluster.local ``` @@ -898,7 +898,7 @@ kubectl --kubeconfig alice exec webserver -- curl http://webserver.gas-productio **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenants oil gas ``` @@ -982,15 +982,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod or container that sets `allowPrivilegeEscalation=true` in its `securityContext`. -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -1012,7 +1012,7 @@ You must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -1099,15 +1099,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod or container that sets privileges in its `securityContext`. -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -1129,7 +1129,7 @@ You must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -1168,7 +1168,7 @@ EOF As tenant owner, check if you can access the persistent volumes -```bash +```bash kubectl --kubeconfig alice auth can-i get persistentvolumes kubectl --kubeconfig alice auth can-i list persistentvolumes kubectl --kubeconfig alice auth can-i watch persistentvolumes @@ -1258,15 +1258,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod mounting the host IPC namespace. -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -1286,7 +1286,7 @@ You must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -1375,15 +1375,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod using `hostNetwork` -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -1401,8 +1401,8 @@ EOF As tenant owner, create a pod defining a container using `hostPort` -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -1423,7 +1423,7 @@ In both the cases above, you must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -1516,15 +1516,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod defining a volume of type `hostpath`. -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -1552,7 +1552,7 @@ You must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -1640,15 +1640,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod mounting the host PID namespace. -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -1668,7 +1668,7 @@ You must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -1710,7 +1710,7 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` @@ -1748,7 +1748,7 @@ NodePort service types are forbidden for the tenant: please, reach out to the sy **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -1794,20 +1794,20 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, retrieve the configured quotas in the tenant namespace: -```bash +```bash kubectl --kubeconfig alice get quota NAME AGE REQUEST LIMIT capsule-oil-0 23s persistentvolumeclaims: 0/100, pods: 0/100, services: 0/50, services.loadbalancers: 0/3, - services.nodeports: 0/20 + services.nodeports: 0/20 ``` Make sure that a quota is configured for API objects: `PersistentVolumeClaim`, `LoadBalancer`, `NodePort`, `Pods`, etc @@ -1815,7 +1815,7 @@ Make sure that a quota is configured for API objects: `PersistentVolumeClaim`, ` **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -1862,18 +1862,18 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, retrieve the configured quotas in the tenant namespace: -```bash +```bash kubectl --kubeconfig alice get quota NAME AGE REQUEST LIMIT -capsule-oil-0 24s requests.cpu: 0/8, requests.memory: 0/16Gi limits.cpu: 0/8, limits.memory: 0/16Gi -capsule-oil-1 24s requests.storage: 0/10Gi +capsule-oil-0 24s requests.cpu: 0/8, requests.memory: 0/16Gi limits.cpu: 0/8, limits.memory: 0/16Gi +capsule-oil-1 24s requests.storage: 0/10Gi ``` Make sure that a quota is configured for CPU, memory, and storage resources. @@ -1881,7 +1881,7 @@ Make sure that a quota is configured for CPU, memory, and storage resources. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -1920,7 +1920,7 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` @@ -1953,7 +1953,7 @@ ImagePullPolicy IfNotPresent for container nginx is forbidden, use one of the fo **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil ``` @@ -1983,7 +1983,7 @@ spec: privileged: false # Required to prevent escalations to root. allowPrivilegeEscalation: false - volumes: + volumes: - 'persistentVolumeClaim' runAsUser: rule: RunAsAny @@ -2040,15 +2040,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod defining a volume of any of the core type except `PersistentVolumeClaim`. For example: -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -2076,7 +2076,7 @@ You must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp @@ -2131,7 +2131,7 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` @@ -2165,7 +2165,7 @@ A valid Storage Class must be used, one of the following (delete-policy) **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete storageclass delete-policy ``` @@ -2257,15 +2257,15 @@ EOF As tenant owner, run the following command to create a namespace in the given tenant -```bash +```bash kubectl --kubeconfig alice create ns oil-production kubectl --kubeconfig alice config set-context --current --namespace oil-production ``` As tenant owner, create a pod or container that does not set `runAsNonRoot` to `true` in its `securityContext`, and `runAsUser` must not be set to 0. -```yaml -kubectl --kubeconfig alice apply -f - << EOF +```yaml +kubectl --kubeconfig alice apply -f - << EOF apiVersion: v1 kind: Pod metadata: @@ -2284,8 +2284,8 @@ You must have the pod blocked by `PodSecurityPolicy`. **Cleanup:** As cluster admin, delete all the created resources -```bash +```bash kubectl --kubeconfig cluster-admin delete tenant oil kubectl --kubeconfig cluster-admin delete PodSecurityPolicy tenant kubectl --kubeconfig cluster-admin delete ClusterRole tenant:psp -``` \ No newline at end of file +``` diff --git a/content/en/docs/proxy/proxysettings.md b/content/en/docs/proxy/proxysettings.md index 8c2ee66..f4c4a52 100644 --- a/content/en/docs/proxy/proxysettings.md +++ b/content/en/docs/proxy/proxysettings.md @@ -69,14 +69,13 @@ spec: A powerful tool to enhance the user-experience for all your users. -## Proxysettings - -`ProxySettings` are created in a namespace of a tenant, if it's not in a namespace of a tenant it's not regarded as valid. With the `ProxySettings` Tenant Owners can further improve the experience for their fellow tenant users. +## Proxysetting +A `ProxySetting` is created in a namespace of a tenant, if it's not in a namespace of a tenant it's not regarded as valid. With the `ProxySetting` Tenant Owners can further improve the experience for their fellow tenant users. ```yaml apiVersion: capsule.clastix.io/v1beta1 -kind: ProxySettings +kind: ProxySetting metadata: name: solar-proxy namespace: solar-prod diff --git a/content/en/docs/proxy/reference.md b/content/en/docs/proxy/reference.md index 21e43e6..1fee53f 100644 --- a/content/en/docs/proxy/reference.md +++ b/content/en/docs/proxy/reference.md @@ -35,6 +35,7 @@ GlobalProxySettings is the Schema for the globalproxysettings API. | **kind** | string | GlobalProxySettings | true | | **[metadata](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#objectmeta-v1-meta)** | object | Refer to the Kubernetes API documentation for the fields of the `metadata` field. | true | | **[spec](#globalproxysettingsspec)** | object | GlobalProxySettingsSpec defines the desired state of GlobalProxySettings. | false | +| **[status](#globalproxysettingsstatus)** | object | GlobalProxySettingsStatus defines the observed state of GlobalProxySettings. | false | ### GlobalProxySettings.spec @@ -118,6 +119,36 @@ relates the key and values. | **operator** | string | operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist. | true | | **values** | []string | values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch. | false | + +### GlobalProxySettings.status + + + +GlobalProxySettingsStatus defines the observed state of GlobalProxySettings. + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **[conditions](#globalproxysettingsstatusconditionsindex)** | []object | Conditions contains the reconciliation conditions for this GlobalProxySettings. | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation observed by the controller.
*Format*: int64
| false | + + +### GlobalProxySettings.status.conditions[index] + + + +Condition contains details for one aspect of the current state of this API Resource. + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **lastTransitionTime** | string | lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
*Format*: date-time
| true | +| **message** | string | message is a human readable message indicating details about the transition.
This may be an empty string. | true | +| **reason** | string | reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty. | true | +| **status** | enum | status of the condition, one of True, False, Unknown.
*Enum*: True, False, Unknown
| true | +| **type** | string | type of condition in CamelCase or in foo.example.com/CamelCase. | true | +| **observedGeneration** | integer | observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
*Format*: int64
*Minimum*: 0
| false | + ## ProxySetting @@ -134,6 +165,7 @@ ProxySetting is the Schema for the proxysettings API. | **kind** | string | ProxySetting | true | | **[metadata](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#objectmeta-v1-meta)** | object | Refer to the Kubernetes API documentation for the fields of the `metadata` field. | true | | **[spec](#proxysettingspec)** | object | ProxySettingSpec defines the additional Capsule Proxy settings for additional users of the Tenant.
Resource is Namespace-scoped and applies the settings to the belonged Tenant. | false | +| **[status](#proxysettingstatus)** | object | ProxySettingStatus defines the observed state of ProxySetting. | false | ### ProxySetting.spec @@ -220,3 +252,33 @@ relates the key and values. | **kind** | enum |
*Enum*: Nodes, StorageClasses, IngressClasses, PriorityClasses, RuntimeClasses, PersistentVolumes
| true | | **operations** | []enum |
*Enum*: List, Update, Delete
| true | + +### ProxySetting.status + + + +ProxySettingStatus defines the observed state of ProxySetting. + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **[conditions](#proxysettingstatusconditionsindex)** | []object | Conditions contains the reconciliation conditions for this ProxySetting. | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation observed by the controller.
*Format*: int64
| false | + + +### ProxySetting.status.conditions[index] + + + +Condition contains details for one aspect of the current state of this API Resource. + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **lastTransitionTime** | string | lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
*Format*: date-time
| true | +| **message** | string | message is a human readable message indicating details about the transition.
This may be an empty string. | true | +| **reason** | string | reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty. | true | +| **status** | enum | status of the condition, one of True, False, Unknown.
*Enum*: True, False, Unknown
| true | +| **type** | string | type of condition in CamelCase or in foo.example.com/CamelCase. | true | +| **observedGeneration** | integer | observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
*Format*: int64
*Minimum*: 0
| false | + diff --git a/content/en/docs/proxy/setup/installation.md b/content/en/docs/proxy/setup/installation.md index 779743f..4e6737c 100644 --- a/content/en/docs/proxy/setup/installation.md +++ b/content/en/docs/proxy/setup/installation.md @@ -66,7 +66,7 @@ spec: project: system source: repoURL: ghcr.io/projectcapsule/charts - targetRevision: {{< capsule_chart_version >}} + targetRevision: {{< capsule_chart_version >}} chart: capsule helm: valuesObject: @@ -177,7 +177,7 @@ spec: ## Considerations -Consdierations when deploying capsule-proxy +Considerations when deploying capsule-proxy ### Exposure @@ -377,7 +377,7 @@ By default, Capsule delegates its certificate management to cert-manager. This i options: generateCertificates: true certManager: - generateCertificates: false + generateCertificates: false ``` #### Distribute CA within the Cluster @@ -402,7 +402,7 @@ How to distribute the CA certificate using External Secrets Operator (ESO). In t First allow ServiceAccount `headlamp` to read the Secret `capsule-proxy` in the `capsule-system` namespace: -```yaml +```yaml --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/content/en/docs/reference.md b/content/en/docs/reference.md index d60415b..6687396 100644 --- a/content/en/docs/reference.md +++ b/content/en/docs/reference.md @@ -69,7 +69,7 @@ CapsuleConfigurationSpec defines the Capsule configuration. | **cacheInvalidation** | string | Define the period of time upon a cache invalidation is executed for all caches.
*Default*: 24h
| true | | **enableTLSReconciler** | boolean | Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks
when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager.
*Default*: false
| true | | **[rbac](#capsuleconfigurationspecrbac)** | object | Define Properties for managed ClusterRoles by Capsule
*Default*: map[]
| true | -| **[administrators](#capsuleconfigurationspecadministratorsindex)** | []object | Define entities which can act as Administrators in the capsule construct
These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label
for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor
be ignored by capsule. | false | +| **[administrators](#capsuleconfigurationspecadministratorsindex)** | []object | Define entities which can act as Administrators in the capsule construct
These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label
for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefore
be ignored by capsule. | false | | **[admission](#capsuleconfigurationspecadmission)** | object | Configuration for dynamic Validating and Mutating Admission webhooks managed by Capsule. | false | | **allowServiceAccountPromotion** | boolean | ServiceAccounts within tenant namespaces can be promoted to owners of the given tenant
this can be achieved by labeling the serviceaccount and then they are considered owners. This can only be done by other owners of the tenant.
However ServiceAccounts which have been promoted to owner can not promote further serviceAccounts.
*Default*: false
| false | | **forceTenantPrefix** | boolean | Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix,
separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
*Default*: false
| false | @@ -666,9 +666,29 @@ CapsuleConfigurationStatus defines the Capsule configuration status. | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | +| **[conditions](#capsuleconfigurationstatusconditionsindex)** | []object | Conditions holds the reconciliation conditions for this CapsuleConfiguration.
Includes a Ready condition indicating whether the configuration was
successfully validated and applied. | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | +| **tenants** | []string | Tenants is the sorted list of Tenant names currently present in the cluster.
The total count is available via len(Tenants). | false | | **[users](#capsuleconfigurationstatususersindex)** | []object | Users which are considered Capsule Users and are bound to the Capsule Tenant construct. | false | +### CapsuleConfiguration.status.conditions[index] + + + +Condition contains details for one aspect of the current state of this API Resource. + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **lastTransitionTime** | string | lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
*Format*: date-time
| true | +| **message** | string | message is a human readable message indicating details about the transition.
This may be an empty string. | true | +| **reason** | string | reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty. | true | +| **status** | enum | status of the condition, one of True, False, Unknown.
*Enum*: True, False, Unknown
| true | +| **type** | string | type of condition in CamelCase or in foo.example.com/CamelCase. | true | +| **observedGeneration** | integer | observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
*Format*: int64
*Minimum*: 0
| false | + + ### CapsuleConfiguration.status.users[index] @@ -814,6 +834,7 @@ CustomQuotaStatus defines the observed state of GlobalResourceQuota. | **[conditions](#customquotastatusconditionsindex)** | []object | Conditions | true | | **[targets](#customquotastatustargetsindex)** | []object | Targeting GVK | true | | **[claims](#customquotastatusclaimsindex)** | []object | Objects regarding this policy | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | | **[usage](#customquotastatususage)** | object | Usage measurements | false | @@ -1074,6 +1095,7 @@ CustomQuotaStatus defines the observed state of GlobalResourceQuota. | **[targets](#globalcustomquotastatustargetsindex)** | []object | Targeting GVK | true | | **[claims](#globalcustomquotastatusclaimsindex)** | []object | Objects regarding this policy | false | | **namespaces** | []string | Observed Namespaces | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | | **[usage](#globalcustomquotastatususage)** | object | Usage measurements | false | @@ -1465,6 +1487,7 @@ GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. | :---- | :---- | :----------- | :-------- | | **size** | integer | How many items are being replicated by the TenantResource. | true | | **[conditions](#globaltenantresourcestatusconditionsindex)** | []object | Condition of the GlobalTenantResource. | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | | **[processedItems](#globaltenantresourcestatusprocesseditemsindex)** | []object | List of the replicated resources for the given TenantResource. | false | | **selectedTenants** | []string | List of Tenants addressed by the GlobalTenantResource. | false | | **[serviceAccount](#globaltenantresourcestatusserviceaccount)** | object | Serviceaccount used for impersonation | false | @@ -1726,6 +1749,7 @@ ResourceQuotaClaimStatus defines the observed state of ResourceQuotaClaim. | **[conditions](#resourcepoolclaimstatusconditionsindex)** | []object | Conditions for the resource claim | true | | **[allocation](#resourcepoolclaimstatusallocation)** | object | Tracks the Usage from Claimed from this claim and available resources | false | | **[condition](#resourcepoolclaimstatuscondition)** | object | Deprecated: Use Conditions | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | | **[pool](#resourcepoolclaimstatuspool)** | object | Reference to the GlobalQuota being claimed from | false | @@ -1928,6 +1952,7 @@ GlobalResourceQuotaStatus defines the observed state of GlobalResourceQuota. | **[exhaustions](#resourcepoolstatusexhaustionskey)** | map[string]object | Exhaustions from claims associated with the pool | false | | **namespaceCount** | integer | How many namespaces are considered
*Default*: 0
| false | | **namespaces** | []string | Namespaces which are considered for claims | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | ### ResourcePool.status.conditions[index] @@ -2011,7 +2036,7 @@ ResourceQuotaClaimStatus defines the observed state of ResourceQuotaClaim. -For future inmplementatiosn where users might manage RuleStatus CRs tehmselves +For future implementation where users might manage RuleStatus CRs themselves | **Name** | **Type** | **Description** | **Required** | @@ -2055,6 +2080,7 @@ RuleStatus contains the accumulated rules applying to namespace it's deployed in | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | | **[conditions](#rulestatusstatusconditionsindex)** | []object | Conditions | true | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | | **[rule](#rulestatusstatusrule)** | object | Managed Enforcement properties per Namespace (aggregated from rules) | false | @@ -2128,7 +2154,7 @@ TenantOwner is the Schema for the tenantowners API. | **kind** | string | TenantOwner | true | | **[metadata](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#objectmeta-v1-meta)** | object | Refer to the Kubernetes API documentation for the fields of the `metadata` field. | true | | **[spec](#tenantownerspec)** | object | spec defines the desired state of TenantOwner. | true | -| **status** | object | status defines the observed state of TenantOwner. | false | +| **[status](#tenantownerstatus)** | object | status defines the observed state of TenantOwner. | false | ### TenantOwner.spec @@ -2145,6 +2171,37 @@ spec defines the desired state of TenantOwner. | **name** | string | Name of the entity. | true | | **clusterRoles** | []string | Defines additional cluster-roles for the specific Owner.
*Default*: [admin capsule-namespace-deleter]
| false | + +### TenantOwner.status + + + +status defines the observed state of TenantOwner. + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **[conditions](#tenantownerstatusconditionsindex)** | []object | Conditions contains the reconciliation conditions for this TenantOwner. | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | +| **tenants** | []string | Tenants lists the names of all Tenants that this TenantOwner is currently matched to
via the Tenant's spec.permissions.matchOwners selectors. | false | + + +### TenantOwner.status.conditions[index] + + + +Condition contains details for one aspect of the current state of this API Resource. + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **lastTransitionTime** | string | lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
*Format*: date-time
| true | +| **message** | string | message is a human readable message indicating details about the transition.
This may be an empty string. | true | +| **reason** | string | reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty. | true | +| **status** | enum | status of the condition, one of True, False, Unknown.
*Enum*: True, False, Unknown
| true | +| **type** | string | type of condition in CamelCase or in foo.example.com/CamelCase. | true | +| **observedGeneration** | integer | observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
*Format*: int64
*Minimum*: 0
| false | + ## TenantResource @@ -2410,6 +2467,7 @@ TenantResourceStatus defines the observed state of TenantResource. | :---- | :---- | :----------- | :-------- | | **size** | integer | How many items are being replicated by the TenantResource. | true | | **[conditions](#tenantresourcestatusconditionsindex)** | []object | Condition of the GlobalTenantResource. | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | | **[processedItems](#tenantresourcestatusprocesseditemsindex)** | []object | List of the replicated resources for the given TenantResource. | false | | **[serviceAccount](#tenantresourcestatusserviceaccount)** | object | Serviceaccount used for impersonation | false | @@ -3652,6 +3710,7 @@ Returns the observed state of the Tenant. | **state** | enum | The operational state of the Tenant. Possible values are "Active", "Cordoned" or "Terminating".
*Enum*: Cordoned, Active, Terminating
*Default*: Active
| true | | **[classes](#tenantstatusclasses)** | object | Available Class Types within Tenant | false | | **namespaces** | []string | List of namespaces assigned to the Tenant. (Deprecated) | false | +| **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | | **[owners](#tenantstatusownersindex)** | []object | Collected owners for this tenant | false | | **[promotions](#tenantstatuspromotionsindex)** | []object | Promoted ServiceAccounts across the Tenant | false | | **[spaces](#tenantstatusspacesindex)** | []object | Tracks state for the namespaces associated with this tenant | false | diff --git a/content/en/docs/resource-management/resourcepools/_index.md b/content/en/docs/resource-management/resourcepools/_index.md index dfbcda4..ef1154f 100644 --- a/content/en/docs/resource-management/resourcepools/_index.md +++ b/content/en/docs/resource-management/resourcepools/_index.md @@ -760,7 +760,7 @@ status: **Do not apply the resourcepools yet, this may lead to workloads not being able to schedule!** {{% /alert %}} -We are now abstracting . For each item, we are creating a `ResourcePool` with the same values. The `ResourcePool` will be scoped to the `Tenant` and will be used for all namespaces in the tenant. Let's first migrate the first item: +We are now abstracting. For each item, we are creating a `ResourcePool` with the same values. The `ResourcePool` will be scoped to the `Tenant` and will be used for all namespaces in the tenant. Let's first migrate the first item: ```yaml --- @@ -929,9 +929,9 @@ Success 🍀 This part should provide you with a little bit of back story, as to why this implementation was done the way it currently is. Let's start. -Since the begining of capsule we are struggeling with a concurrency probelm regarding `ResourcesQuotas`, this was already early detected in [Issue 49](https://github.com/projectcapsule/capsule/issues/49). Let's quickly recap what really the problem is with the current `ResourceQuota` centric approach. +Since the beginning of capsule we are struggling with a concurrency problem regarding `ResourcesQuotas`, this was already early detected in [Issue 49](https://github.com/projectcapsule/capsule/issues/49). Let's quickly recap what really the problem is with the current `ResourceQuota` centric approach. -With the current `ResourceQuota` with `Scope: Tenant` we encounter the problem, that resourcequotas spread across multiple namespaces refering to one tenant quota can be overprovisioned, if an operation is executed in parallel (eg. total is `services/count: 3`, in each namespace you could then create 3 services, leading to a possible overprovision of hard `* amount-namespaces`). The Problem in this approach is, that we are not doing anything with Webhooks, therefor we rely on the speed of the controller, where this entire construct becomes a matter of luck and racing conditions. +With the current `ResourceQuota` with `Scope: Tenant` we encounter the problem, that resourcequotas spread across multiple namespaces referring to one tenant quota can be overprovisioned, if an operation is executed in parallel (eg. total is `services/count: 3`, in each namespace you could then create 3 services, leading to a possible overprovision of hard `* amount-namespaces`). The Problem in this approach is, that we are not doing anything with Webhooks, therefore we rely on the speed of the controller, where this entire construct becomes a matter of luck and racing conditions. So, there needs to be change. But times have also changed and we have listened to our users, so the new approach to `ResourceQuotas` should: @@ -960,7 +960,7 @@ Here we have the problem, that even if we would block resourcequota status updat This way it's only possible to scheduled "ordered". In conclusion this would also downscale the resourcequota when the resources are no longer needed. This is how `ResourceQuotas` from the Kubernetes Core-API reject workload, when you try to allocate a Quantity in a namespaces, but the `ResourceQuota` does not have enough space. -But there's some problems with this approach as well: +But there are some problems with this approach as well: * if you eg. schedule a pod and the quota is `count/0` there's no admission call on the resourcequota, which would be the easiest. So we would need to find a way to know, there's something new requesting resources. For example [Rancher](https://ranchermanager.docs.rancher.com/how-to-guides/new-user-guides/manage-clusters/projects-and-namespaces#4-optional-add-resource-quotas) works around this problem with namespaced `DefaultLimits`. But this is not the agile approach we would like to offer. * The only indication that I know of is that we get an Event, which we can intercept with admission (`ResourceQuota Denied`), regarding quotaoverprovision. diff --git a/content/en/docs/tenants/administration.md b/content/en/docs/tenants/administration.md index 1761166..2daeafb 100644 --- a/content/en/docs/tenants/administration.md +++ b/content/en/docs/tenants/administration.md @@ -14,7 +14,7 @@ Bill needs to cordon a `Tenant` and its `Namespaces` for several reasons: * During incidents or outages * During planned maintenance of a dedicated nodes pool in a BYOD scenario -With the default installation of Capsule all `CREATE`, `UPDATE` and `DELETE` operations performed by **[Capsule Users](/docs/operating/architecture/#capsule-users)** are droped. Any Updates to Subresources (i.e. `status` updates) and events are allowed to proceed as usual. If you wish to allow specific Operations, you can change the values for the Cordoning Admission via Values (eg. allow `Pod/DELETE` operations): +With the default installation of Capsule all `CREATE`, `UPDATE` and `DELETE` operations performed by **[Capsule Users](/docs/operating/architecture/#capsule-users)** are dropped. Any Updates to Subresources (i.e. `status` updates) and events are allowed to proceed as usual. If you wish to allow specific Operations, you can change the values for the Cordoning Admission via Values (eg. allow `Pod/DELETE` operations): ```yaml webhooks: diff --git a/content/en/docs/tenants/permissions.md b/content/en/docs/tenants/permissions.md index 63e3b37..e218773 100644 --- a/content/en/docs/tenants/permissions.md +++ b/content/en/docs/tenants/permissions.md @@ -47,7 +47,7 @@ To explain these entries, let's inspect one of them: * `kind`: It can be [User](#users), [Group](#groups) or [ServiceAccount](#serviceaccounts) * `name`: Is the reference name of the user, group or serviceaccount we want to bind -* `clusterRoles`: ClusterRoles which are bound for each namespace of teh tenant to the owner. By default, Capsule assigns `admin` and `capsule-namespace-deleter` roles to each owner, but you can customize them as explained in [Owner Roles](#owner-roles) section. +* `clusterRoles`: ClusterRoles which are bound for each namespace of the tenant to the owner. By default, Capsule assigns `admin` and `capsule-namespace-deleter` roles to each owner, but you can customize them as explained in [Owner Roles](#owner-roles) section. With this information available you @@ -180,7 +180,7 @@ status: name: alice ``` -We. can see that the `system:serviceaccount:capsule:controller` ServiceAccount now has additional `mega-admin` and `controller` roles assigned. +We can see that the `system:serviceaccount:capsule:controller` ServiceAccount now has additional `mega-admin` and `controller` roles assigned. #### Implicit Tenant Assignment @@ -347,7 +347,7 @@ kubectl auth can-i create namespaces yes ``` -All the groups you want to promot to `TenantOwners` must be part of the Group Scope. You have to add `solar-users` to the CapsuleConfiguration [Group Scope](#group-scope) to make it work. +All the groups you want to promote to `TenantOwners` must be part of the Group Scope. You have to add `solar-users` to the CapsuleConfiguration [Group Scope](#group-scope) to make it work. ### ServiceAccounts diff --git a/content/en/docs/tenants/quickstart.md b/content/en/docs/tenants/quickstart.md index 7162bfa..de187f9 100644 --- a/content/en/docs/tenants/quickstart.md +++ b/content/en/docs/tenants/quickstart.md @@ -155,7 +155,7 @@ User "alice" cannot list resource "pods" in API group "" in the namespace "kube- As Cluster Administrator we want to avoid that tenants can communicate between each other. To achieve that, we can use [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) to isolate the namespaces created by different tenants. -Let's ensure for any `Tenant` and any of it's future namespaces, that each gits a [NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/), which does not allow ingress/egress traffic to/from other tenants. +Let's ensure for any `Tenant` and any of its future namespaces, that each gets a [NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/), which does not allow ingress/egress traffic to/from other tenants. ```yaml apiVersion: capsule.clastix.io/v1beta2 diff --git a/content/en/ecosystem/_index.md b/content/en/ecosystem/_index.md index 6c5d1b7..5b41a0b 100644 --- a/content/en/ecosystem/_index.md +++ b/content/en/ecosystem/_index.md @@ -14,6 +14,6 @@ Capsule works well with other [CNCF](https://landscape.cncf.io/) kubernetes base ## Addons -Addons are seperate projects which interact with the core Capsule Project. Since [our commitment](/project/commitment) is, to have a stable core API we decided to push towards an addon based ecosystem. If you have a new addon, which interacts with the capsule core project, consider [adding the addon](/project/contributions/addons/). +Addons are separate projects which interact with the core Capsule Project. Since [our commitment](/project/commitment) is, to have a stable core API we decided to push towards an addon based ecosystem. If you have a new addon, which interacts with the capsule core project, consider [adding the addon](/project/contributions/addons/). {{< addons >}} \ No newline at end of file diff --git a/content/en/ecosystem/integrations/kyverno.md b/content/en/ecosystem/integrations/kyverno.md index d4bce38..04bda40 100644 --- a/content/en/ecosystem/integrations/kyverno.md +++ b/content/en/ecosystem/integrations/kyverno.md @@ -1,6 +1,6 @@ --- title: Kyverno -describtion: Capsule interation with Kyverno +description: Capsule integration with Kyverno logo: "https://github.com/cncf/artwork/raw/refs/heads/main/projects/kyverno/icon/color/kyverno-icon-color.svg" type: single display: true @@ -140,7 +140,7 @@ spec: ### Using Global Configuration -When creating a a lot of policies, you might want to abstract your configuration into a global configuration. This is a good practice to avoid duplication and to have a single source of truth. Also if we introduce breaking changes (like changing the label name), we only have to change it in one place. Here is an example of a global configuration: +When creating a lot of policies, you might want to abstract your configuration into a global configuration. This is a good practice to avoid duplication and to have a single source of truth. Also if we introduce breaking changes (like changing the label name), we only have to change it in one place. Here is an example of a global configuration: ```yaml apiVersion: v1 @@ -171,7 +171,7 @@ spec: - name: Validate HelmRelease/Kustomization Target Namespace context: - # Load Gloabl Configuration + # Load Global Configuration - name: global configMap: name: kyverno-global-config diff --git a/content/en/ecosystem/integrations/lens.md b/content/en/ecosystem/integrations/lens.md index 61a3ae7..603c154 100644 --- a/content/en/ecosystem/integrations/lens.md +++ b/content/en/ecosystem/integrations/lens.md @@ -1,6 +1,6 @@ --- title: Lens -describtion: Capsule extension for Lens +description: Capsule extension for Lens type: single --- @@ -13,5 +13,5 @@ Capsule extension for Lens provides these capabilities: * List all tenants * See tenant details and change through the embedded Lens editor * Check Resources Quota and Budget at both the tenant and namespace level - -Please, see the [README](https://github.com/clastix/capsule-lens-extension) for details about the installation of the Capsule Lens Extension. \ No newline at end of file + +Please, see the [README](https://github.com/clastix/capsule-lens-extension) for details about the installation of the Capsule Lens Extension. diff --git a/content/en/ecosystem/integrations/monitoring.md b/content/en/ecosystem/integrations/monitoring.md index ee336aa..240c0de 100644 --- a/content/en/ecosystem/integrations/monitoring.md +++ b/content/en/ecosystem/integrations/monitoring.md @@ -1,6 +1,6 @@ --- title: Monitoring -describtion: Capsule interation with Monitoring Solutions +description: Capsule integration with Monitoring Solutions display: false --- diff --git a/content/en/ecosystem/integrations/tekton.md b/content/en/ecosystem/integrations/tekton.md index cc445f9..e66a339 100644 --- a/content/en/ecosystem/integrations/tekton.md +++ b/content/en/ecosystem/integrations/tekton.md @@ -1,6 +1,6 @@ --- title: Tekton -describtion: Capsule interation with Tekton +description: Capsule integration with Tekton logo: https://avatars.githubusercontent.com/u/47602533?s=200&v=4 type: single display: false @@ -9,7 +9,7 @@ integration: true With Capsule extension for [Lens](https://github.com/lensapp/lens), a cluster administrator can easily manage from a single pane of glass all resources of a Kubernetes cluster, including all the Tenants created through the Capsule Operator. -## Prerequisites +## Prerequisites Tekton must be already installed on your cluster, if that's not the case consult the documentation here: @@ -27,7 +27,7 @@ Now for the enduser experience we are going to deploy the tekton dashboard. When - [Tekton Dashboard](https://github.com/tektoncd/dashboard/blob/main/docs/walkthrough/walkthrough-oauth2-proxy.md) -Once that is done, we need to make small adjustments to the `tekton-dashboard` service account. +Once that is done, we need to make small adjustments to the `tekton-dashboard` service account. **kustomization.yaml** ```yaml @@ -88,13 +88,13 @@ patches: ``` -This patch assumes there's a secret called `capsule-proxy` with the CA certificate for the Capsule Proxy URL. +This patch assumes there's a secret called `capsule-proxy` with the CA certificate for the Capsule Proxy URL. Apply the given kustomization: - + extraEnv: - name: KUBERNETES_SERVICE_HOST @@ -104,7 +104,7 @@ extraEnv: -### Tekton Operator +### Tekton Operator When using the [Tekton Operator](https://tekton.dev/docs/operator/), you need to add the following to the `TektonConfig`: @@ -145,6 +145,6 @@ spec: value: "capsule-proxy.capsule-system.svc" - name: KUBERNETES_SERVICE_PORT value: "9001" -``` - -See for reference the [options spec](https://tekton.dev/docs/operator/tektonconfig/#additional-fields-as-options) \ No newline at end of file +``` + +See for reference the [options spec](https://tekton.dev/docs/operator/tektonconfig/#additional-fields-as-options) diff --git a/content/en/ecosystem/integrations/teleport.md b/content/en/ecosystem/integrations/teleport.md index 47f2892..b859f13 100644 --- a/content/en/ecosystem/integrations/teleport.md +++ b/content/en/ecosystem/integrations/teleport.md @@ -1,6 +1,6 @@ --- title: Teleport -describtion: Capsule Proxy interation with Teleport +description: Capsule Proxy integration with Teleport logo: https://avatars.githubusercontent.com/u/10781132?s=200&v=4 type: single display: true diff --git a/content/en/project/contributions/guidelines.md b/content/en/project/contributions/guidelines.md index 076c031..5e12368 100644 --- a/content/en/project/contributions/guidelines.md +++ b/content/en/project/contributions/guidelines.md @@ -15,13 +15,13 @@ Versions follow [Semantic Versioning](https://semver.org/) terminology and are e Security fixes, may be backported to the three most recent minor releases, depending on severity and feasibility. -Prereleases are marked as `-rc.x` (release candidate) and may refere to any type of version bump. +Prereleases are marked as `-rc.x` (release candidate) and may refer to any type of version bump. ## Pull Requests The pull request title is checked according to the described [semantics](#semantics) (pull requests don't require a scope). However pull requests are currently not used to generate the changelog. Check if your pull requests body meets the following criteria: -- reference a previously opened issue: https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls#issues-and-pull-requests +- reference a previously opened issue: https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls#issues-and-pull-requests - splitting changes into several and documented small commits - limit the git subject to 50 characters and write as the continuation of the sentence "If applied, this commit will ..." @@ -80,7 +80,7 @@ To reorganise your commits, do the following (or use your way of doing it): 1. Pull upstream changes - + ```bash git remote add upstream git@github.com:projectcapsule/capsule.git git pull upstream main @@ -160,7 +160,7 @@ The semantics should indicate the change and it's impact. The general format for The following types are allowed for commits and pull requests: * `chore`: housekeeping changes, no production code change - * `ci`: changes to buillding process/workflows + * `ci`: changes to building process/workflows * `docs`: changes to documentation * `feat`: new features * `fix`: bug fixes From e15e04a4067f4afe183a96c8314cb42850fcc8e4 Mon Sep 17 00:00:00 2001 From: Sander Tervoert <32864332+sandert-k8s@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:37:47 +0200 Subject: [PATCH 5/9] feat: add prek as precommit (#92) * feat: add prek as pre-commit Signed-off-by: sandert-k8s * chore: fix broken links Signed-off-by: sandert-k8s * chore: fix linting with prek Signed-off-by: sandert-k8s --------- Signed-off-by: sandert-k8s Signed-off-by: Oliver Baehler --- DEVELOPMENT.md | 79 +++++++++++++++++++ content/dictionary.txt | 2 +- content/en/adopters/_index.md | 2 +- content/en/docs/_index.md | 0 content/en/docs/guides/_index.md | 2 +- .../namespace-migration-across-tenants.md | 1 - content/en/docs/operating/_index.md | 2 +- .../en/docs/operating/admission-policies.md | 2 - content/en/docs/operating/architecture.md | 2 +- .../docs/operating/best-practices/_index.md | 1 - .../docs/operating/best-practices/images.md | 2 +- .../operating/best-practices/networking.md | 7 +- content/en/docs/operating/setup/_index.md | 2 +- content/en/docs/operating/setup/rancher.md | 1 - content/en/docs/operating/troubleshoting.md | 2 +- content/en/docs/overview/_index.md | 4 +- content/en/docs/proxy/gangplank.md | 2 +- content/en/docs/proxy/setup/installation.md | 2 - content/en/docs/replications/tenant.md | 2 - content/en/docs/resource-management/_index.md | 0 .../customquotas/_index.md | 10 +-- content/en/docs/tenants/_index.md | 2 +- content/en/docs/tenants/enforcement.md | 4 +- content/en/docs/tenants/metadata.md | 2 +- content/en/docs/tenants/quickstart.md | 9 +-- content/en/docs/tenants/rules.md | 12 +-- content/en/docs/whats-new.md | 4 +- content/en/ecosystem/_index.md | 2 +- content/en/ecosystem/integrations/argocd.md | 4 +- .../en/ecosystem/integrations/dashboard.md | 1 - .../ecosystem/integrations/envoy-gateway.md | 1 - content/en/ecosystem/integrations/eso.md | 7 -- content/en/ecosystem/integrations/opencost.md | 2 +- .../en/ecosystem/integrations/openshift.md | 2 +- content/en/ecosystem/integrations/rancher.md | 2 +- content/en/ecosystem/integrations/velero.md | 2 +- content/en/project/branding.md | 2 +- content/en/project/commitment.md | 2 +- content/en/project/contributions/addons.md | 10 +-- content/en/project/contributions/adoption.md | 6 +- content/en/project/governance.md | 14 ++-- prek.toml | 37 +++++++++ scripts/apidocs-check.sh | 19 +++++ scripts/lychee.sh | 21 +++++ 44 files changed, 212 insertions(+), 82 deletions(-) create mode 100644 DEVELOPMENT.md mode change 100755 => 100644 content/en/docs/_index.md mode change 100755 => 100644 content/en/docs/resource-management/_index.md create mode 100644 prek.toml create mode 100755 scripts/apidocs-check.sh create mode 100755 scripts/lychee.sh diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..6b95427 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,79 @@ +# Development + +## Pre-commit Hooks + +This repository uses [prek](https://prek.j178.dev/) to run pre-commit hooks locally. `prek` is a fast, Rust-native drop-in replacement for [pre-commit](https://pre-commit.com/). + +### Installation + +Install `prek` via Homebrew: + +```bash +brew install prek lychee +``` + +For other installation options, see the [prek installation docs](https://prek.j178.dev/installation/). + +### Setup + +After cloning the repository, install the Git shims so hooks run automatically: + +```bash +prek install # runs hooks on every commit +prek install --hook-type pre-push # runs lychee link checker on every push +``` + +### Running Hooks Manually + +Run all hooks against staged files: + +```bash +prek run +``` + +Run all hooks against the entire repository: + +```bash +prek run --all-files +``` + +Run a specific hook by ID: + +```bash +prek run trailing-whitespace +``` + +Run only the link checker: + +```bash +prek run --hook-stage pre-push lychee +``` + +Run only the API docs check: + +```bash +prek run --hook-stage pre-push apidocs-check +``` + +### Configured Hooks + +| Hook | Stage | Description | +|------|-------|-------------| +| `check-executables-have-shebangs` | commit | Ensures executable files have a shebang line | +| `end-of-file-fixer` | commit | Ensures files end with a newline | +| `trailing-whitespace` | commit | Removes trailing whitespace | +| `check-yaml` | commit | Validates YAML syntax | +| `check-merge-conflict` | commit | Detects leftover merge conflict markers | +| `yamllint` | commit | Lints YAML files for style issues | +| `lychee` | push | Checks for broken links in `content/` | +| `apidocs-check` | push | Regenerates API docs and fails if they are out of date | + +All commit-stage hooks run only against files under `content/`. + +### Updating Hook Versions + +To update pinned hook repository revisions: + +```bash +prek auto-update +``` diff --git a/content/dictionary.txt b/content/dictionary.txt index 204fcde..f12c7ce 100644 --- a/content/dictionary.txt +++ b/content/dictionary.txt @@ -216,4 +216,4 @@ webhook webhooks wontfix CNCF -mb-4 \ No newline at end of file +mb-4 diff --git a/content/en/adopters/_index.md b/content/en/adopters/_index.md index 0d687ba..7671ad5 100644 --- a/content/en/adopters/_index.md +++ b/content/en/adopters/_index.md @@ -7,4 +7,4 @@ All entries on this page were added by people who worked on these companies or b --- -{{< adopters >}} \ No newline at end of file +{{< adopters >}} diff --git a/content/en/docs/_index.md b/content/en/docs/_index.md old mode 100755 new mode 100644 diff --git a/content/en/docs/guides/_index.md b/content/en/docs/guides/_index.md index 281c499..6d4a2fe 100644 --- a/content/en/docs/guides/_index.md +++ b/content/en/docs/guides/_index.md @@ -6,4 +6,4 @@ weight: 11 Capsule does not care about the authentication strategy used in the cluster and all the Kubernetes methods of authentication are supported. The only requirement to use Capsule is to assign tenant users to the group defined by userGroups option in the CapsuleConfiguration, which defaults to capsule.clastix.io. -In the following guide, we'll use [Keycloak](https://www.keycloak.org/) an Open Source Identity and Access Management server capable to authenticate users via OIDC and release JWT tokens as proof of authentication. \ No newline at end of file +In the following guide, we'll use [Keycloak](https://www.keycloak.org/) an Open Source Identity and Access Management server capable to authenticate users via OIDC and release JWT tokens as proof of authentication. diff --git a/content/en/docs/guides/namespace-migration-across-tenants.md b/content/en/docs/guides/namespace-migration-across-tenants.md index acf6970..59d86f4 100644 --- a/content/en/docs/guides/namespace-migration-across-tenants.md +++ b/content/en/docs/guides/namespace-migration-across-tenants.md @@ -37,4 +37,3 @@ NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE solar Active 0 2m22s wind Active 2 2m15s ``` - diff --git a/content/en/docs/operating/_index.md b/content/en/docs/operating/_index.md index 2c52f40..d408a51 100644 --- a/content/en/docs/operating/_index.md +++ b/content/en/docs/operating/_index.md @@ -8,4 +8,4 @@ cascade: - type: "docs" _target: path: "/**" ---- \ No newline at end of file +--- diff --git a/content/en/docs/operating/admission-policies.md b/content/en/docs/operating/admission-policies.md index ac98a07..291bffc 100644 --- a/content/en/docs/operating/admission-policies.md +++ b/content/en/docs/operating/admission-policies.md @@ -1062,5 +1062,3 @@ spec: operator: NotEquals value: "{{clusterIssuerManagedBy}}"{{< /tab >}} {{% /tabpane %}} - - diff --git a/content/en/docs/operating/architecture.md b/content/en/docs/operating/architecture.md index 4ded795..a1f04c5 100644 --- a/content/en/docs/operating/architecture.md +++ b/content/en/docs/operating/architecture.md @@ -131,7 +131,7 @@ With this approach you share the nodes amongst all Tenants, therefore giving you ![Shared Nodepool](/images/content/scheduling-shared.drawio.png) -We provide the concept of [ResourcePools](/docs/resourcepools/) to manage resources cross namespaces. There's some further aspects you must think about with shared approaches: +We provide the concept of [ResourcePools](/docs/resource-management/resourcepools/) or [CustomQuotas](/docs/resource-management/customquotas/) to manage resources cross namespaces. There's some further aspects you must think about with shared approaches: * [PriorityClasses](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) * [ResourceQuotas](https://kubernetes.io/docs/concepts/policy/resource-quotas/) diff --git a/content/en/docs/operating/best-practices/_index.md b/content/en/docs/operating/best-practices/_index.md index 32bacdd..ad5eaeb 100644 --- a/content/en/docs/operating/best-practices/_index.md +++ b/content/en/docs/operating/best-practices/_index.md @@ -33,4 +33,3 @@ If no external secret store is available, there should at least be a secure way In our ecosystem, we provide a solution based on SOPS (Secrets OPerationS) for this use case; called the [sops-operator](https://github.com/peak-scale/sops-operator). [👉 Demonstration](https://killercoda.com/peakscale/course/playgrounds/sops-secrets) - diff --git a/content/en/docs/operating/best-practices/images.md b/content/en/docs/operating/best-practices/images.md index 8cded34..9bd5ead 100644 --- a/content/en/docs/operating/best-practices/images.md +++ b/content/en/docs/operating/best-practices/images.md @@ -6,4 +6,4 @@ description: Multi-Tenant Container Images considerations > [Until this issue is resolved (might be in Kubernetes 1.34)](https://github.com/kubernetes/enhancements/issues/2535) -it's recommended to use the [ImagePullPolicy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) `Always` for private registries on shared nodes. This ensures that no images can be used which are already pulled to the node. \ No newline at end of file +it's recommended to use the [ImagePullPolicy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) `Always` for private registries on shared nodes. This ensures that no images can be used which are already pulled to the node. diff --git a/content/en/docs/operating/best-practices/networking.md b/content/en/docs/operating/best-practices/networking.md index 6f38d02..e9f37fb 100644 --- a/content/en/docs/operating/best-practices/networking.md +++ b/content/en/docs/operating/best-practices/networking.md @@ -118,7 +118,7 @@ spec: - fromNamespaces: - matchLabels: company.com/system: "true" # System namespaces (monitoring, ingress, etc.) - + egress: - toCIDR: - 10.96.0.10/32 # kube-dns IP @@ -128,11 +128,8 @@ spec: protocol: UDP - port: "53" protocol: TCP - + - toNamespaces: - matchLabels: capsule.clastix.io/tenant: "{{tenant.name}}" # Egress to all tenant namespaces ``` - - - diff --git a/content/en/docs/operating/setup/_index.md b/content/en/docs/operating/setup/_index.md index af41962..c7430b5 100644 --- a/content/en/docs/operating/setup/_index.md +++ b/content/en/docs/operating/setup/_index.md @@ -4,4 +4,4 @@ weight: 1 description: "Setting up your environment for Capsule" type: docs weight: 1 ---- \ No newline at end of file +--- diff --git a/content/en/docs/operating/setup/rancher.md b/content/en/docs/operating/setup/rancher.md index ab51dd1..aa5f55b 100644 --- a/content/en/docs/operating/setup/rancher.md +++ b/content/en/docs/operating/setup/rancher.md @@ -404,4 +404,3 @@ Configure an OIDC authentication provider, with Client with issuer, return URLs 1. In Rancher as an administrator, set the user custom role with `get` of Cluster. 1. In Rancher as an administrator, add the Rancher user ID of the just-logged in user as Owner of a `Tenant`. 1. (optional) configure `proxySettings` for the `Tenant` to enable tenant users to access cluster-wide resources. - diff --git a/content/en/docs/operating/troubleshoting.md b/content/en/docs/operating/troubleshoting.md index b44c88d..62c8d1d 100644 --- a/content/en/docs/operating/troubleshoting.md +++ b/content/en/docs/operating/troubleshoting.md @@ -4,4 +4,4 @@ weight: 15 description: "Different topics when you encounter problems with Capsule" --- - \ No newline at end of file + diff --git a/content/en/docs/overview/_index.md b/content/en/docs/overview/_index.md index caee1a8..3d69a39 100644 --- a/content/en/docs/overview/_index.md +++ b/content/en/docs/overview/_index.md @@ -23,7 +23,7 @@ Capsule introduces the **Tenant**: a lightweight, cluster-scoped resource that g Everything defined on a Tenant is automatically inherited by all its namespaces: - **RBAC bindings** - roles are propagated to every namespace without manual setup. -- **Resource quotas & limits** - CPU, memory, and storage budgets managed at the tenant level via [Resource Pools](/docs/resourcepools/). +- **Resource quotas & limits** - CPU, memory, and storage budgets managed at the tenant level via [Resource Pools](/docs/resource-management/resourcepools/) or [Custom Quotas](/docs/resource-management/customquotas/). - **Admission rules** - allowed image registries, pull policies, security contexts, and more. - **Templated resource distribution** - using [Replications](/docs/replications/), resources such as NetworkPolicies, ImagePullSecrets, and LimitRanges are automatically distributed into all namespaces a Tenant Owner creates, using Go templates for dynamic values like namespace name or tenant name. @@ -42,7 +42,7 @@ This shift-left model means Tenant Owners handle day-to-day namespace operations ## Key Features - **[Tenants & Namespaces](/docs/tenants/)**: Group namespaces into logical units per team, product, or customer. Policy inheritance is automatic. -- **[Resource Pools](/docs/resourcepools/)**: Distribute CPU, memory, and storage budgets across namespaces with flexible claiming rather than fixed per-namespace quotas. +- **[Resource Management](/docs/resource-management/)**: Distribute CPU, memory, and storage budgets across namespaces with flexible claiming rather than fixed per-namespace quotas. - **[Replications](/docs/replications/)**: Propagate Kubernetes resources (Secrets, ConfigMaps, etc.) across tenant namespaces automatically. - **[Policy Rules](/docs/tenants/rules/)**: Enforce allowed registries, pull policies, and namespace metadata requirements on a per-tenant basis. - **[Capsule Proxy](/docs/proxy/)**: Let users run `kubectl get namespaces` and see only their own, without granting cluster-wide LIST permissions. Also works for other cluster-wide requests, like `kubectl get pods -A`, or listing Persistent Volumes that are used by a Persistent Volume Claim inside the tenant. diff --git a/content/en/docs/proxy/gangplank.md b/content/en/docs/proxy/gangplank.md index f0d9063..23fbec3 100644 --- a/content/en/docs/proxy/gangplank.md +++ b/content/en/docs/proxy/gangplank.md @@ -112,4 +112,4 @@ gangplank: items: - key: ca.crt path: ca.crt -``` \ No newline at end of file +``` diff --git a/content/en/docs/proxy/setup/installation.md b/content/en/docs/proxy/setup/installation.md index 4e6737c..fbd9b56 100644 --- a/content/en/docs/proxy/setup/installation.md +++ b/content/en/docs/proxy/setup/installation.md @@ -503,5 +503,3 @@ capsule_proxy_requests_total counts the global requests that Capsule Proxy is pa path: the HTTP path of every single request that Capsule Proxy passes to the upstream status: the HTTP status code of the request - - diff --git a/content/en/docs/replications/tenant.md b/content/en/docs/replications/tenant.md index fdfaff1..1e20f9f 100644 --- a/content/en/docs/replications/tenant.md +++ b/content/en/docs/replications/tenant.md @@ -1294,5 +1294,3 @@ Currently mainly the conditions of `TenantResources` are exposed as metrics: capsule_resource_condition{condition="Cordoned",name="templated-forbidden-namespace",target_namespace="e2e-tenantresource-ssa-system"} 0 capsule_resource_condition{condition="Ready",name="templated-forbidden-namespace",target_namespace="e2e-tenantresource-ssa-system"} 1 ``` - - diff --git a/content/en/docs/resource-management/_index.md b/content/en/docs/resource-management/_index.md old mode 100755 new mode 100644 diff --git a/content/en/docs/resource-management/customquotas/_index.md b/content/en/docs/resource-management/customquotas/_index.md index b50b99e..cca3ae3 100644 --- a/content/en/docs/resource-management/customquotas/_index.md +++ b/content/en/docs/resource-management/customquotas/_index.md @@ -43,7 +43,7 @@ For each matching quota it: This makes quota enforcement safe even during bursts of concurrent requests. -> **Without the Admission Webhook enabled, CustomQuotas are observational only.** +> **Without the Admission Webhook enabled, CustomQuotas are observational only.** > The controllers still rebuild and report usage, but requests are not denied. By default, no objects are sent to this webhook. You must explicitly enable it and configure matching rules. @@ -103,7 +103,7 @@ webhooks: expression: '!has(request.subResource) || request.subResource == ""' - name: ignore-events expression: 'request.resource.resource != "events"' - + # Execlude Entities which never count towards quotas to avoid performance issues and disruptions in case of issues with the webhook (Example). - name: 'exclude-kubelet-requests' expression: '!("system:nodes" in request.userInfo.groups)' @@ -122,9 +122,9 @@ The following constraints apply to the JSONPath: * Expressions must start with a dot (`.`) and use standard JSONPath syntax. (valid `.spec.storage.usage`). * Paths can not be empty. * The maximum length of the path is `1024` characters. - * Expressions can not contain any of the following characters: + * Expressions can not contain any of the following characters: * `\n` (newline) - * `\r` (carriage return) + * `\r` (carriage return) * `\t` (tab) * Values can resolve to array results, which are then summed up. (For example, `.spec.containers[*].resources.limits.cpu` would sum the CPU limits of all containers in a Pod.) * Missing fields are resulting in an error, as it's assumed that if a path requires calculation it should force the targeted sources to define these paths. Meaning if you eg define this JP `.spec.initContainers[*].resources.limits.cpu` on a Pod that has no initContainers, it will error. If you want to only calculate the path if it exists, you can use a [fielselector](#fieldselectors) to only match objects where the path exists, for example with `.spec.initContainers` as fieldSelector. @@ -1144,4 +1144,4 @@ capsule_custom_quota_resource_usage{custom_quota="pod-count-limit",target_namesp capsule_custom_quota_resource_item_usage{custom_quota="pod-count-limit",group="",kind="Pod",name="nginx-deployment-77bc6bd484-4qm4h",target_namespace="wind-test"} 1 capsule_custom_quota_resource_item_usage{custom_quota="pod-count-limit",group="",kind="Pod",name="nginx-deployment-77bc6bd484-bsnfz",target_namespace="wind-test"} 1 capsule_custom_quota_resource_item_usage{custom_quota="pod-count-limit",group="",kind="Pod",name="nginx-deployment-77bc6bd484-f8qcv",target_namespace="wind-test"} 1 -``` \ No newline at end of file +``` diff --git a/content/en/docs/tenants/_index.md b/content/en/docs/tenants/_index.md index ccde9e7..b51e3cb 100644 --- a/content/en/docs/tenants/_index.md +++ b/content/en/docs/tenants/_index.md @@ -13,4 +13,4 @@ Capsule is a framework to implement multi-tenant and policy-driven scenarios in * **Joe**: works as a lead developer of a distributed team in Alice's organization. * **Bob**: is the head of engineering for the Water department, the main and historical line of business at Acme Corp. -This scenario will guide you through the following topics. \ No newline at end of file +This scenario will guide you through the following topics. diff --git a/content/en/docs/tenants/enforcement.md b/content/en/docs/tenants/enforcement.md index b696c6f..033e892 100644 --- a/content/en/docs/tenants/enforcement.md +++ b/content/en/docs/tenants/enforcement.md @@ -279,10 +279,10 @@ kubectl apply -f - << EOF apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: - name: tenant-default + name: tenant-default labels: env: "production" -handler: myconfiguration +handler: myconfiguration EOF ``` diff --git a/content/en/docs/tenants/metadata.md b/content/en/docs/tenants/metadata.md index 281db0f..a812c75 100644 --- a/content/en/docs/tenants/metadata.md +++ b/content/en/docs/tenants/metadata.md @@ -60,7 +60,7 @@ spec: - key: env operator: In values: ["prod"] - enforce: + enforce: registries: - url: "harbor/v2/prod-registry/.*" policy: [ "ifNotPresent" ] diff --git a/content/en/docs/tenants/quickstart.md b/content/en/docs/tenants/quickstart.md index de187f9..d084458 100644 --- a/content/en/docs/tenants/quickstart.md +++ b/content/en/docs/tenants/quickstart.md @@ -40,7 +40,7 @@ metadata: name: platform-team labels: team: platform - + spec: kind: Group name: "oidc:kubernetes:admin" @@ -137,7 +137,7 @@ kubectl --as alice --as-group projectcapsule.dev create namespace solar-developm And operate with fully admin permissions: ```bash -kubectl -n solar-development run nginx --image=docker.io/nginx +kubectl -n solar-development run nginx --image=docker.io/nginx kubectl -n solar-development get pods ``` @@ -210,8 +210,3 @@ spec: matchLabels: capsule.clastix.io/tenant: "{{tenant.name}}" ``` - - - - - diff --git a/content/en/docs/tenants/rules.md b/content/en/docs/tenants/rules.md index 3862423..6e23863 100644 --- a/content/en/docs/tenants/rules.md +++ b/content/en/docs/tenants/rules.md @@ -32,7 +32,7 @@ spec: - key: env operator: In values: ["prod"] - enforce: + enforce: registries: - url: "harbor/v2/prod-registry/.*" policy: [ "ifNotPresent" ] @@ -57,17 +57,17 @@ apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: solar -spec: +spec: ... rules: - permissions: promotions: # With this rule every promoted ServiceAccount get's the ClusterRole "tenant-replicator" in all Namespaces of the Tenant solar - - clusterRoles: + - clusterRoles: - "configmap-replicator" - + # With this rule every promoted ServiceAccount with the matching labels get's the ClusterRole "tenant-replicator" in all Namespaces of the Tenant solar - - clusterRoles: + - clusterRoles: - "secret-replicator" selector: matchLabels: @@ -81,7 +81,7 @@ spec: permissions: promotions: # With this rule every promoted ServiceAccount with the matching labels get's the ClusterRole "tenant-replicator" in namespaces of the Tenant solar matching the selector (env=prod) - - clusterRoles: + - clusterRoles: - "secret-replicator:prod" ``` diff --git a/content/en/docs/whats-new.md b/content/en/docs/whats-new.md index 2ebdb26..9f73749 100644 --- a/content/en/docs/whats-new.md +++ b/content/en/docs/whats-new.md @@ -66,7 +66,7 @@ Newly added documentation to integrate Capsule with other applications: * [CoreDNS Plugin](https://github.com/CorentinPtrl/capsule_coredns) (Community Contribution) * [Argo CD](/ecosystem/integrations/argocd/) -* [Flux CD](/ecosystem/integrations/fluxcd/) +* [Flux CD](/docs/guides/use-fluxcd.md) ## Project Updates 💫 @@ -90,4 +90,4 @@ In the upcoming releases we are planning to work on the following features: ## Events 📅 * **Capsule Roundtable Summer 2026 🇨🇭** - * We are planning to host a Capsule Roundtable in Summer 2026 in Switzerland (**28. Mai 2026**). The exact date and location will be announced soon, but we are looking forward to meeting the community in person and discussing the future of Capsule. If you are interested in attending or want to know more about the event, [feel free to reach out to us](https://peakscale.ch/en/contact/). The event is intended for users to present their use-cases and share their experiences with the project, as well as for us to present the roadmap and gather feedback from the community (Not a sales event). \ No newline at end of file + * We are planning to host a Capsule Roundtable in Summer 2026 in Switzerland (**28. Mai 2026**). The exact date and location will be announced soon, but we are looking forward to meeting the community in person and discussing the future of Capsule. If you are interested in attending or want to know more about the event, [feel free to reach out to us](https://peakscale.ch/en/contact/). The event is intended for users to present their use-cases and share their experiences with the project, as well as for us to present the roadmap and gather feedback from the community (Not a sales event). diff --git a/content/en/ecosystem/_index.md b/content/en/ecosystem/_index.md index 5b41a0b..50b03b0 100644 --- a/content/en/ecosystem/_index.md +++ b/content/en/ecosystem/_index.md @@ -16,4 +16,4 @@ Capsule works well with other [CNCF](https://landscape.cncf.io/) kubernetes base Addons are separate projects which interact with the core Capsule Project. Since [our commitment](/project/commitment) is, to have a stable core API we decided to push towards an addon based ecosystem. If you have a new addon, which interacts with the capsule core project, consider [adding the addon](/project/contributions/addons/). -{{< addons >}} \ No newline at end of file +{{< addons >}} diff --git a/content/en/ecosystem/integrations/argocd.md b/content/en/ecosystem/integrations/argocd.md index 8c0bbfe..fb12b4d 100644 --- a/content/en/ecosystem/integrations/argocd.md +++ b/content/en/ecosystem/integrations/argocd.md @@ -49,7 +49,7 @@ resource.customizations.actions.Namespace: | return false end if not has_managed_ownerref() then - return {} + return {} end local labels = {} if obj.metadata ~= nil and obj.metadata.labels ~= nil then @@ -173,7 +173,7 @@ resource.customizations.health.capsule.clastix.io_Tenant: | end end end - + hs.status = "Progressing" hs.message = "Waiting for Status" return hs diff --git a/content/en/ecosystem/integrations/dashboard.md b/content/en/ecosystem/integrations/dashboard.md index e28efd7..96d4b16 100644 --- a/content/en/ecosystem/integrations/dashboard.md +++ b/content/en/ecosystem/integrations/dashboard.md @@ -166,4 +166,3 @@ Now you can install the Kubernetes Dashboard: helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/ helm install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard -n ${KUBERNETES_DASHBOARD_NAMESPACE} -f values-kubernetes-dashboard.yaml ``` - diff --git a/content/en/ecosystem/integrations/envoy-gateway.md b/content/en/ecosystem/integrations/envoy-gateway.md index 6174de5..99f2d97 100644 --- a/content/en/ecosystem/integrations/envoy-gateway.md +++ b/content/en/ecosystem/integrations/envoy-gateway.md @@ -161,4 +161,3 @@ spec: namespace: {{tenant.name}}-system sectionName: http-challenge ``` - diff --git a/content/en/ecosystem/integrations/eso.md b/content/en/ecosystem/integrations/eso.md index 48a6d12..c791f0f 100644 --- a/content/en/ecosystem/integrations/eso.md +++ b/content/en/ecosystem/integrations/eso.md @@ -51,10 +51,3 @@ One possible approach is to generate a ClusterSecretStore scoping it only to the ``` - - - - - - - diff --git a/content/en/ecosystem/integrations/opencost.md b/content/en/ecosystem/integrations/opencost.md index 2844a23..5cc8717 100644 --- a/content/en/ecosystem/integrations/opencost.md +++ b/content/en/ecosystem/integrations/opencost.md @@ -110,4 +110,4 @@ Aggregation is only possible via the API. There is no option to aggregate using -d window=1h \ -d aggregate=deployment,annotation:opencost_projectcapsule_dev_tenant \ -d resolution=1h - ``` \ No newline at end of file + ``` diff --git a/content/en/ecosystem/integrations/openshift.md b/content/en/ecosystem/integrations/openshift.md index 2287b0d..6f066d8 100644 --- a/content/en/ecosystem/integrations/openshift.md +++ b/content/en/ecosystem/integrations/openshift.md @@ -5,4 +5,4 @@ logo: "https://logosandtypes.com/wp-content/uploads/2020/07/openshift.png" type: single display: true integration: true ---- \ No newline at end of file +--- diff --git a/content/en/ecosystem/integrations/rancher.md b/content/en/ecosystem/integrations/rancher.md index e195291..a49bf47 100644 --- a/content/en/ecosystem/integrations/rancher.md +++ b/content/en/ecosystem/integrations/rancher.md @@ -5,4 +5,4 @@ logo: "https://www.rancher.com/assets/img/logos/rancher-suse-logo-stacked-color. type: single display: true integration: true ---- \ No newline at end of file +--- diff --git a/content/en/ecosystem/integrations/velero.md b/content/en/ecosystem/integrations/velero.md index 6dd21e3..b622efc 100644 --- a/content/en/ecosystem/integrations/velero.md +++ b/content/en/ecosystem/integrations/velero.md @@ -4,4 +4,4 @@ link: "/docs/operating/backup-restore/" logo: https://velero.io/docs/main/img/velero.png display: true integration: true ---- \ No newline at end of file +--- diff --git a/content/en/project/branding.md b/content/en/project/branding.md index c1669cd..00a0b2b 100644 --- a/content/en/project/branding.md +++ b/content/en/project/branding.md @@ -12,4 +12,4 @@ menu: Find all the available artworks and logos for the project in the CNCF Logo repository: - * [https://github.com/cncf/artwork/tree/main/projects/capsule](https://github.com/cncf/artwork/tree/main/projects/capsule) \ No newline at end of file + * [https://github.com/cncf/artwork/tree/main/projects/capsule](https://github.com/cncf/artwork/tree/main/projects/capsule) diff --git a/content/en/project/commitment.md b/content/en/project/commitment.md index 1b84ea3..6bd951e 100644 --- a/content/en/project/commitment.md +++ b/content/en/project/commitment.md @@ -18,4 +18,4 @@ Our commitment is to deliver a robust, stable Tenancy specification that serves * **Clear and Consistent API Specification**: Empowering developers with a transparent and reliable interface. * **Community-Driven Automation**: Enabling and encouraging further automation initiatives from the community. -Please note, this project is offered without any warranty, and support is provided on a best-effort basis. \ No newline at end of file +Please note, this project is offered without any warranty, and support is provided on a best-effort basis. diff --git a/content/en/project/contributions/addons.md b/content/en/project/contributions/addons.md index 7290b3f..af10422 100644 --- a/content/en/project/contributions/addons.md +++ b/content/en/project/contributions/addons.md @@ -12,9 +12,9 @@ In the [addons.yaml](https://github.com/projectcapsule/website/blob/main/data/ad 1. Fork the [projectcapsule/capsule](https://github.com/projectcapsule/capsule/fork) project to your personal space. 2. Clone it locally with `git clone https://github.com//capsule.git`. -3. We prefer using the logo from an upstream resource instead of collecting logos. If you don't have a logo to provide you will get a default logo. +3. We prefer using the logo from an upstream resource instead of collecting logos. If you don't have a logo to provide you will get a default logo. 4. Open `docs/data/addons.yaml` following format: - + ```yaml # Addon Name - name: "Example Addon" @@ -28,11 +28,11 @@ In the [addons.yaml](https://github.com/projectcapsule/website/blob/main/data/ad ``` - + > You can just add to the end of the file, we already sort alphabetically by name of organization. - + 1. Save the file, then do `git add` -A and commit using `git commit -s -m "chore(docs): add my-addon to addons"` [See our contribution guidelines](/project/contributions/guidelines/). 2. Push the commit with `git push origin main`. 3. Open a Pull Request to [projectcapsule/capsule](https://github.com/projectcapsule/capsule/pulls) and a preview build will turn up. - + Thanks a lot for being part of our community - we very much appreciate it! diff --git a/content/en/project/contributions/adoption.md b/content/en/project/contributions/adoption.md index d50021d..9f544d0 100644 --- a/content/en/project/contributions/adoption.md +++ b/content/en/project/contributions/adoption.md @@ -11,13 +11,13 @@ Have your tried Capsule or are you using it in your project or company? Please c In the [adopters.yaml](https://github.com/projectcapsule/capsule/blob/main/ADOPTERS.md) file you can add yourself as an adopter of the project. You just need to add an entry for your company and upon merging it will automatically be added to our website. To add your organization follow these steps: 1. Fork the [projectcapsule/capsule](https://github.com/projectcapsule/website/fork) project to your personal space. - + 2. Clone it locally with `git clone https://github.com//capsule.git`. 3. We prefer using the logo from an upstream resource instead of collecting logos. If you don't have a logo to provide you will get a default logo. 4. Open `docs/data/adopters.yaml` file and add your organization to the list. The file is in the following format: - + ```yaml # Company/Project Name - name: "Adopter Name" @@ -34,5 +34,5 @@ In the [adopters.yaml](https://github.com/projectcapsule/capsule/blob/main/ADOPT 2. Push the commit with `git push origin main`. 3. Open a Pull Request to [projectcapsule/capsule](https://github.com/projectcapsule/capsule/pulls) and a preview build will turn up. - + Thanks a lot for being part of our community - we very much appreciate it! diff --git a/content/en/project/governance.md b/content/en/project/governance.md index c4cd3f2..c771f64 100644 --- a/content/en/project/governance.md +++ b/content/en/project/governance.md @@ -87,7 +87,7 @@ Maintainers who are selected will be granted the necessary GitHub rights. Maintainers may resign at any time if they feel that they will not be able to continue fulfilling their project duties. -Maintainers may also be removed after being inactive, failure to fulfill their +Maintainers may also be removed after being inactive, failure to fulfill their Maintainer responsibilities, violating the Code of Conduct, or other reasons. A Maintainer may be removed at any time by a 2/3 vote of the remaining maintainers. @@ -98,7 +98,7 @@ and can be rapidly returned to Maintainer status if their availability changes. ## Meetings Time zones permitting, Maintainers are expected to participate in the public -developer meeting and/or public discussions. +developer meeting and/or public discussions. Maintainers will also have closed meetings in order to discuss security reports or Code of Conduct violations. Such meetings should be scheduled by any @@ -120,7 +120,7 @@ violations by community members will be discussed and resolved in private Mainta The Maintainers will appoint a Security Response Team to handle security reports. This committee may simply consist of the Maintainer Council themselves. If this -responsibility is delegated, the Maintainers will appoint a team of at least two +responsibility is delegated, the Maintainers will appoint a team of at least two contributors to handle it. The Maintainers will review who is assigned to this at least once a year. @@ -129,15 +129,15 @@ holes and breaches according to the [security policy](TODO:Link to security.md). ## Voting -While most business in Capsule Project is conducted by "[lazy consensus](https://community.apache.org/committers/lazyConsensus.html)", +While most business in Capsule Project is conducted by "[lazy consensus](https://community.apache.org/committers/lazyConsensus.html)", periodically the Maintainers may need to vote on specific actions or changes. Any Maintainer may demand a vote be taken. Most votes require a simple majority of all Maintainers to succeed, except where -otherwise noted. Two-thirds majority votes mean at least two-thirds of all +otherwise noted. Two-thirds majority votes mean at least two-thirds of all existing maintainers. ## Modifying this Charter -Changes to this Governance and its supporting documents may be approved by -a 2/3 vote of the Maintainers. \ No newline at end of file +Changes to this Governance and its supporting documents may be approved by +a 2/3 vote of the Maintainers. diff --git a/prek.toml b/prek.toml new file mode 100644 index 0000000..d3854bc --- /dev/null +++ b/prek.toml @@ -0,0 +1,37 @@ +[[repos]] +repo = "https://github.com/pre-commit/pre-commit-hooks" +rev = "v6.0.0" +hooks = [ + { id = "check-executables-have-shebangs", files = "^content/", exclude = "^content/en/docs/(proxy/)?reference\\.md$" }, + { id = "end-of-file-fixer", files = "^content/", exclude = "^content/en/docs/(proxy/)?reference\\.md$" }, + { id = "trailing-whitespace", files = "^content/", exclude = "^content/en/docs/(proxy/)?reference\\.md$" }, + { id = "check-yaml", files = "^content/", exclude = "^content/en/docs/(proxy/)?reference\\.md$" }, + { id = "check-merge-conflict", files = "^content/", exclude = "^content/en/docs/(proxy/)?reference\\.md$" }, +] + +[[repos]] +repo = "https://github.com/adrienverge/yamllint" +rev = "v1.38.0" +hooks = [ + { id = "yamllint", files = "^content/", exclude = "^content/en/docs/(proxy/)?reference\\.md$" }, +] + +[[repos]] +repo = "local" + +[[repos.hooks]] +id = "lychee" +name = "Check links with lychee" +entry = "scripts/lychee.sh" +args = ["--config", "config/lychee.toml", "--max-concurrency", "2", "--accept", "200,429", "--timeout", "60", "./content"] +language = "system" +stages = ["pre-push"] +pass_filenames = false + +[[repos.hooks]] +id = "apidocs-check" +name = "Check API docs are up to date" +entry = "scripts/apidocs-check.sh" +language = "system" +stages = ["pre-push"] +pass_filenames = false diff --git a/scripts/apidocs-check.sh b/scripts/apidocs-check.sh new file mode 100755 index 0000000..41b6238 --- /dev/null +++ b/scripts/apidocs-check.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Regenerate API docs and fail if they are not up to date with the committed files. +set -euo pipefail + +tmp_capsule="$(mktemp -d)" +tmp_proxy="$(mktemp -d)" +cleanup() { + rm -rf "$tmp_capsule" "$tmp_proxy" +} +trap cleanup EXIT + +make TARGET_DIR="$tmp_capsule" apidocs-capsule +make TARGET_DIR="$tmp_proxy" apidocs-capsule-proxy + +if ! git diff --quiet -- content/en/docs/reference.md content/en/docs/proxy/reference.md; then + echo ">>> API docs are out of date. Run 'make apidocs' and commit the result." + git --no-pager diff -- content/en/docs/reference.md content/en/docs/proxy/reference.md + exit 1 +fi diff --git a/scripts/lychee.sh b/scripts/lychee.sh new file mode 100755 index 0000000..2191bae --- /dev/null +++ b/scripts/lychee.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Mirror CI: Hugo serves /static as /, so lychee needs images copied into content/ +set -euo pipefail + +had_images_dir=false +if [[ -d content/en/images ]]; then + had_images_dir=true +else + mkdir -p content/en/images +fi + +cleanup() { + if [[ "$had_images_dir" == false ]]; then + rm -rf content/en/images + fi +} +trap cleanup EXIT + +cp -R static/images/. content/en/images/ + +lychee "$@" From 233d9cab12b671e8da6ac58f0bd5b847cd87da50 Mon Sep 17 00:00:00 2001 From: Oliver Baehler Date: Tue, 9 Jun 2026 23:46:49 +0200 Subject: [PATCH 6/9] fix: improve rules api Signed-off-by: Oliver Baehler --- .../en/docs/operating/setup/installation.md | 13 + content/en/docs/tenants/rules.md | 521 ++++++++++++++---- 2 files changed, 420 insertions(+), 114 deletions(-) diff --git a/content/en/docs/operating/setup/installation.md b/content/en/docs/operating/setup/installation.md index c677253..f542521 100644 --- a/content/en/docs/operating/setup/installation.md +++ b/content/en/docs/operating/setup/installation.md @@ -98,6 +98,19 @@ rules: ... ``` +Alternatively you can directly grant more permissions via Helm values: + +```yaml +manager: + rbac: + strict: true + clusterRole: + extraResources: + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch", "update", "patch"] +``` + If you are missing permissions you will see an error status for the respective tenants reflecting ```bash diff --git a/content/en/docs/tenants/rules.md b/content/en/docs/tenants/rules.md index 6e23863..0dace3b 100644 --- a/content/en/docs/tenants/rules.md +++ b/content/en/docs/tenants/rules.md @@ -22,9 +22,10 @@ spec: rules: # Matches all Namespaces and enforces the rule for all of them - enforce: - registries: - - url: "harbor/v2/customer-registry/.*" - policy: [ "ifNotPresent" ] + workload: + registries: + - exp: "harbor/v2/customer-registry/.*" + policy: [ "ifNotPresent" ] # Select a subset of namespaces (enviornment=prod) to allow further registries - namespaceSelector: @@ -33,9 +34,10 @@ spec: operator: In values: ["prod"] enforce: - registries: - - url: "harbor/v2/prod-registry/.*" - policy: [ "ifNotPresent" ] + workloads: + registries: + - exp: "harbor/v2/prod-registry/.*" + policy: [ "ifNotPresent" ] ``` Note that rules are combined together. In the above example, all namespaces within the `solar` tenant will be enforced to use images from `harbor/v2/customer-registry/*`, while namespaces labeled with `env=prod` will also be allowed to pull images from `harbor/v2/prod-registry/*`. @@ -43,6 +45,7 @@ Note that rules are combined together. In the above example, all namespaces with ## Permissions Declare permission distribution rules for the selected namespaces. + ### Promotions As an administrator, you can define promotion rules . A promotion rule selects ServiceAccounts within a Tenant based on specified conditions and assigns them predefined ClusterRoles. @@ -170,15 +173,67 @@ To revoke the promotion, Alice can just remove the label: kubectl label sa gitops-reconcile -n solar-test projectcapsule.dev/promote- --as alice --as-group projectcapsule.dev ``` +## Enforcement +Namespace rules can enforce admission behavior for selected resources in Tenant namespaces. Each rule block can define an `action` and one or more matchers. For registry enforcement, matchers are evaluated against the full OCI reference string, including registry, path, tag, or digest. +Rules are evaluated in declaration order. If multiple `allow` or `deny` rules match the same reference, the later matching `allow` or `deny` rule has higher precedence. `audit` rules do not deny the request; they emit an event and add a warning to the admission response. +### Action -## Enforcement +Each `enforce` block supports an `action` field: + +| Action | Behavior | +|---|---| +| `allow` | Allows the matching reference. If `policy` is configured, the pull policy must also be one of the configured values. | +| `deny` | Denies the matching reference. A later matching `allow` rule can override it. | +| `audit` | Allows the request, emits a Kubernetes event, and returns an admission warning. | + +If `action` is omitted, it defaults to `deny`. + +This precedence model allows both broad defaults and specific exceptions. For example, you can allow all Harbor images but deny a customer path afterwards: + +```yaml +rules: + - enforce: + action: allow + workloads: + registries: + - exp: "harbor/.*" -### Registries + - enforce: + action: deny + workloads: + registries: + - exp: "harbor/customer/.*" +``` + +In this example, `harbor/nginx:1.14.2` is allowed, while `harbor/customer/app:1.0.0` is denied because the later, more specific deny rule also matches. -Define allowed image registries for `Pods` with rules. Each registry can have specific policies and validation targets. We use Regexp pattern matching for the registry URL, so you can specify patterns like `harbor/v2/customer-registry/*` to match all images from that registry. The matching is done against the full image name, including the path and tag. The ordering is based on the order of declaration, so the first matching rule will be applied. The later the match is found, the higher the precedence. This allows you to build constructs like these: +You can also deny broadly and allow a more specific exception afterwards: + +```yaml +rules: + - enforce: + action: deny + workloads: + registries: + - exp: "harbor/customer/.*" + + - enforce: + action: allow + workloads: + registries: + - exp: "harbor/customer/prod-image/.*" +``` + +In this example, `harbor/customer/test-image/app:1.0.0` is denied, while `harbor/customer/prod-image/app:1.0.0` is allowed. + +#### Audit + +Use `action: audit` to observe workload usage without blocking the request. Audit rules allow the admission request, emit a Kubernetes event, and add a warning to the admission response. + +For registry enforcement: ```yaml --- @@ -190,62 +245,129 @@ spec: ... rules: - enforce: + action: audit + workloads: + targets: + - pod/containers + registries: + - exp: "docker.io/.*" +``` + +Applying a Pod with `docker.io/library/nginx:latest` succeeds, but the API server response contains an admission warning and Capsule emits a related event for the Pod. + +For QoS enforcement: + +```yaml +rules: + - enforce: + action: audit + workloads: + qosClasses: + - Burstable +``` + +Applying a `Burstable` Pod succeeds, but Capsule emits an event and returns an admission warning. + + + +### Workloads + +Enforcement for workloads mainly targets `Pods` and their associated resources. + +Workload enforcement is configured under `spec.rules[].enforce.workloads`. Each rule can define an `action`, optional workload `targets`, and one or more workload matchers such as registry expressions or QoS classes. + +Rules are evaluated in declaration order. If multiple `allow` or `deny` rules match the same request, the **last matching allow or deny rule wins**. `audit` rules do not block the request; they emit a Kubernetes event and add an admission warning. + +Supported actions are: + +| Action | Behavior | +|---|---| +| `allow` | Allows the matching request. If the matcher defines additional constraints, such as image pull policy, those constraints must also be satisfied. | +| `deny` | Denies the matching request. | +| `audit` | Allows the request, emits a Kubernetes event, and returns an admission warning. | + +If `action` is omitted, Capsule treats the rule as `deny`. + +--- + +#### Targets + +The `targets` field defines which parts of a workload a rule applies to. + +Targets are configured under `enforce.workloads.targets` and are authoritative for target-aware workload enforcement. Registry entries no longer define their own validation targets. + +```yaml +rules: + - enforce: + action: deny + workloads: + targets: + - pod/containers registries: + - exp: "harbor/customer/.*" +``` - # Enforce PullPolicy "always" for all registries (For Container Images and Volume Images) - - url: ".*" - policy: [ "Always" ] +If `targets` is omitted or empty, the rule applies to all workload targets supported by the matching hook. - # If we are pulling from a harbor registry we want to verify the images for Containers only - - url: "harbor/.*" +Supported workload targets are: - - namespaceSelector: - matchExpressions: - - key: env - operator: In - values: ["prod"] - enforce: +| Target | Description | +|---|---| +| `pod/initcontainers` | Applies to images used by `spec.initContainers`. | +| `pod/containers` | Applies to images used by `spec.containers`. | +| `pod/ephemeralcontainers` | Applies to images used by `spec.ephemeralContainers`. | +| `pod/volumes` | Applies to image volumes under `spec.volumes[].image`. | + +Targets are currently used only by a subset of workload hooks. For example, the registry enforcement hook uses targets to decide which Pod image references are validated. The QoS enforcement hook also respects workload targets when evaluating QoS rules. Other hooks may ignore `targets` until they explicitly support target-aware enforcement. + +Examples: + +```yaml +rules: + - enforce: + action: deny + workloads: + targets: + - pod/initcontainers registries: - - url: "harbor/v2/customer-registry/prod-image/.*" - policy: [ "Always" ] + - exp: "harbor/init-only/.*" ``` -Let's try to apply the following pod in the namespace `solar-test` (Does not match the `env=prod` selector): +This rule denies matching images only when they are used by `initContainers`. The same image reference is not denied when used by regular containers, ephemeral containers, or image volumes unless another rule matches those targets. ```yaml -apiVersion: v1 -kind: Pod -metadata: - name: image-volume -spec: - containers: +rules: + - enforce: + action: deny + workloads: + targets: + - pod/containers + - pod/ephemeralcontainers + registries: + - exp: "debug/.*" +``` - - name: shell - command: ["sleep", "infinity"] - imagePullPolicy: Never - image: harbor/v2/prod-registry/debian - volumeMounts: - - name: volume - mountPath: /volume +This rule applies to regular containers and ephemeral containers, but not to init containers or image volumes. - volumes: - - name: volume - image: - reference: quay.io/crio/artifact:v2 - pullPolicy: IfNotPresent +--- +#### QoS Classes -``` +QoS class enforcement allows tenants to allow, deny, or audit Pods based on their [computed Kubernetes QoS class](https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/). -**What do you expect to happen?** +QoS rules are configured under `enforce.workloads.qosClasses`. -```bash -kubectl apply -f pod.yaml -n solar-test +Supported QoS classes are: -Error from server (Forbidden): error when creating "pod.yaml": admission webhook "pods.projectcapsule.dev" denied the request: containers[0] reference "harbor/v2/prod-registry/debian" uses pullPolicy=Never which is not allowed (allowed: Always) -``` +| QoS class | Description | +|---|---| +| `Guaranteed` | The Pod has CPU and memory requests and limits set so that requests equal limits. | +| `Burstable` | The Pod has at least one CPU or memory request or limit, but does not qualify as `Guaranteed`. | +| `BestEffort` | The Pod has no CPU or memory requests or limits. | -Because our first rule enforces all registries to use the `always` image pull policy, the pod creation is denied because it uses the `Never` policy. We can either allow the `Never` policy for this specific registry: +Capsule evaluates the QoS class of the incoming Pod during create and update admission. Pod-level resources are considered when present. If Kubernetes has already populated `status.qosClass`, Capsule can use that value; otherwise it computes the QoS class from the Pod specification. + +Deny `BestEffort` Pods: ```yaml --- @@ -257,28 +379,91 @@ spec: ... rules: - enforce: - registries: + action: deny + workloads: + qosClasses: + - BestEffort +``` - # Enforce PullPolicy "always" for all registries (For Container Images and Volume Images) - - url: ".*" - policy: [ "Always" ] +With this rule, a Pod without CPU or memory requests and limits is denied: - # If we are pulling from a harbor registry we want to verify the images for Containers only - - url: "harbor/.*" - policy: [ "Never" ] +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: best-effort +spec: + containers: + - name: shell + image: harbor/platform/debian:latest + command: ["sleep", "infinity"] +``` - - namespaceSelector: - matchExpressions: - - key: env - operator: In - values: ["prod"] - enforce: - registries: - - url: "harbor/v2/customer-registry/prod-image/.*" - policy: [ "always" ] +Example rejection: + +```bash +Error from server (Forbidden): error when creating "pod.yaml": admission webhook "pods.projectcapsule.dev" denied the request: pod "best-effort" uses QoS class "BestEffort" which is denied by namespace rule +``` + +Audit `Burstable` Pods: + +```yaml +rules: + - enforce: + action: audit + workloads: + qosClasses: + - Burstable +``` + +A matching Pod is admitted, but Capsule emits an event and the API server response contains an admission warning. + +Allow `BestEffort` only for selected namespaces: + +```yaml +rules: + - enforce: + action: deny + workloads: + qosClasses: + - BestEffort + + - namespaceSelector: + matchLabels: + allow-best-effort: "true" + enforce: + action: allow + workloads: + qosClasses: + - BestEffort ``` -But let's for now also remove the generic rule for all registries and only keep the harbor one: +Because later matching allow or deny rules take precedence, namespaces labeled `allow-best-effort=true` can run `BestEffort` Pods, while other namespaces cannot. + +You can also combine QoS rules with targets: + +```yaml +rules: + - enforce: + action: deny + workloads: + targets: + - pod/containers + qosClasses: + - BestEffort +``` + +If `targets` is omitted, the QoS rule applies to all workload targets supported by the QoS hook. + +--- + +#### Registries + +Define image registry rules for `Pods` with regular expressions. The `exp` field is matched against the full OCI reference string. This includes the registry, repository path, image name, tag, and digest if present. + +Registry rules are configured under `enforce.workloads.registries`. The workload-level `targets` field under `enforce.workloads.targets` controls which Pod image references are validated. + +The following example allows Harbor images by default, denies a more specific customer path for regular containers and image volumes, audits regular container images from an audit registry, and allows a production image path only for namespaces matching `env=prod`: ```yaml --- @@ -290,11 +475,27 @@ spec: ... rules: - enforce: - registries: + action: allow + workloads: + registries: + - exp: "harbor/.*" + + - enforce: + action: deny + workloads: + targets: + - pod/containers + - pod/volumes + registries: + - exp: "harbor/customer/.*" - # If we are pulling from a harbor registry we want to verify the images for Containers only - - url: "harbor/.*" - policy: [ "Never" ] + - enforce: + action: audit + workloads: + targets: + - pod/containers + registries: + - exp: "audit/.*" - namespaceSelector: matchExpressions: @@ -302,56 +503,93 @@ spec: operator: In values: ["prod"] enforce: - registries: - - url: "harbor/v2/customer-registry/prod-image/.*" - policy: [ "Always" ] + action: allow + workloads: + targets: + - pod/containers + - pod/volumes + registries: + - exp: "harbor/customer/prod-image/.*" + policy: ["Always"] ``` -If we try to apply the pod again, it we will still get an error. The problem is that we are mounting an image volume that is not coming from the allowed harbor registry: +Let's try to apply the following Pod in namespace `solar-test`, which does not match the `env=prod` selector: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: image-volume +spec: + containers: + - name: shell + command: ["sleep", "infinity"] + imagePullPolicy: IfNotPresent + image: harbor/customer/test-image/debian:latest + volumeMounts: + - name: volume + mountPath: /volume + + volumes: + - name: volume + image: + reference: quay.io/crio/artifact:v2 + pullPolicy: IfNotPresent +``` + +**What do you expect to happen?** ```bash -Error from server (Forbidden): error when creating "pod.yaml": admission webhook "pods.projectcapsule.dev" denied the request: volumes[0](volume) reference "quay.io/crio/artifact:v2" is not allowed +kubectl apply -f pod.yaml -n solar-test + +Error from server (Forbidden): error when creating "pod.yaml": admission webhook "pods.projectcapsule.dev" denied the request: containers[0] reference "harbor/customer/test-image/debian:latest" is denied by registry rule "harbor/customer/.*" ``` -However we would like to only validate the images used in the Pod spec (Containers, InitContainers, EphemeralContainers) and not the ones used in the Volumes. We can achieve this by specifying the [`validation`](#validation) field for the harbor registry: +The Pod is denied because the regular container image matches both `harbor/.*` and `harbor/customer/.*`. Since the deny rule is declared later, it has higher precedence. + +The image volume reference is not denied by the shown deny rule because it does not match `harbor/customer/.*`. If the image volume used a matching reference, for example `harbor/customer/volume-artifact:v1`, the same deny rule would apply because it targets both `pod/containers` and `pod/volumes`. + +In a namespace matching `env=prod`, the more specific production allow rule is also considered: ```yaml ---- -apiVersion: capsule.clastix.io/v1beta2 -kind: Tenant +apiVersion: v1 +kind: Pod metadata: - name: solar + name: prod-image spec: - ... - rules: - - enforce: - registries: - # If we are pulling from a harbor registry we want to verify the images for Containers only - - url: "harbor/.*" - policy: [ "Never" ] - validation: - - "pod/images" + containers: + - name: shell + command: ["sleep", "infinity"] + imagePullPolicy: Always + image: harbor/customer/prod-image/debian:latest +``` - - namespaceSelector: - matchExpressions: - - key: env - operator: In - values: ["prod"] - enforce: +The request is allowed because the namespace-specific rule matches later and allows `harbor/customer/prod-image/.*` with `imagePullPolicy: Always`. + +Target-specific registry rules allow different behavior for different parts of the same Pod. For example, this rule denies the registry only for init containers: + +```yaml +rules: + - enforce: + action: deny + workloads: + targets: + - pod/initcontainers registries: - - url: "harbor/v2/customer-registry/prod-image/.*" - policy: [ "Always" ] + - exp: "harbor/init-only/.*" ``` -#### Policy +A matching reference under `spec.initContainers` is denied. The same reference under `spec.containers` is ignored by this rule. -Define the allowed image pull policies for the specified registry URL. Supported policies are: +##### Policy - * `Always`: The image is always pulled. - * `IfNotPresent`: The image is pulled only if it is not already present on the node. - * `Never`: The image is never pulled. If the image is not present on the node, the Pod will fail to start. +Define the allowed image pull policies for a matching registry expression. Supported policies are: -This configuration is optional. If no policy is specified, all image pull policies are allowed for the given registry. +* `Always`: The image is always pulled. +* `IfNotPresent`: The image is pulled only if it is not already present on the node. +* `Never`: The image is never pulled. If the image is not present on the node, the Pod fails to start. + +The `policy` field is optional. If no policy is specified, all image pull policies are accepted for the matching registry expression. ```yaml --- @@ -362,22 +600,43 @@ metadata: spec: ... rules: - # Matches all Namespaces and enforces the rule for all of them - enforce: + action: allow + workloads: + targets: + - pod/containers + registries: + - exp: "harbor/v2/customer-registry/.*" + policy: ["IfNotPresent", "Always"] +``` + +If a matching `allow` rule defines `policy`, the Pod must use one of the configured pull policies. For example, this rule allows the registry but only with `Always`: + +```yaml +rules: + - enforce: + action: allow + workloads: + targets: + - pod/containers registries: - - url: "harbor/v2/customer-registry/.*" - policy: [ "IfNotPresent", "Always" ] + - exp: "harbor/v2/customer-registry/.*" + policy: ["Always"] ``` +A Pod using `imagePullPolicy: Never` for that registry is rejected: -#### Validation +```bash +Error from server (Forbidden): error when creating "pod.yaml": admission webhook "pods.projectcapsule.dev" denied the request: containers[0] reference "harbor/v2/customer-registry/debian:latest" uses pullPolicy=Never which is not allowed (allowed: Always) +``` -Define on which parts of the Pod the registry policy must be validated. Currently supported validation targets are: +Policy is checked only after the final registry decision is `allow`. A final `deny` decision always denies the request, regardless of the configured pull policy. - * `pod/images`: Validate the images used in the Pod spec (For `Containers`, `InitContainers` and `EphemeralContainers`). - * `pod/volumes`: Validate the images used in the Pod `Volumes` ([Read More](https://kubernetes.io/docs/tasks/configure-pod-container/image-volumes/)) +##### Negated regular expressions -**By default, both targets are validated**. You can override this behavior by specifying the `validation` field for each registry: +A registry expression can be negated with `negate: true`. This means the rule matches references that do **not** match the expression. + +For example, the following rule denies every regular container image that is not from the trusted registry path: ```yaml --- @@ -388,10 +647,44 @@ metadata: spec: ... rules: - # Matches all Namespaces and enforces the rule for all of them - enforce: + action: deny + workloads: + targets: + - pod/containers + registries: + - exp: "trusted/.*" + negate: true +``` + +With this rule: + +* `trusted/backend/api:1.0.0` is allowed because it does not match the negated rule. +* `docker.io/library/nginx:latest` is denied because it does not match `trusted/.*`, so the negated expression evaluates to true. + +You can combine negation with namespace selectors and action precedence. For example, deny all untrusted container images by default, but allow a controlled exception in production namespaces: + +```yaml +rules: + - enforce: + action: deny + workloads: + targets: + - pod/containers registries: - - url: "harbor/v2/customer-registry/.*" - validation: - - "pod/images" + - exp: "trusted/.*" + negate: true + + - namespaceSelector: + matchLabels: + env: prod + enforce: + action: allow + workloads: + targets: + - pod/containers + registries: + - exp: "partner-registry/prod-approved/.*" ``` + +In a namespace labeled `env=prod`, `partner-registry/prod-approved/app:1.0.0` is allowed because the later matching allow rule overrides the earlier deny rule. From 6c3174d421b0fe183f2d5769ce1928feffd41d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= <26610571+oliverbaehler@users.noreply.github.com> Date: Mon, 8 Jun 2026 10:34:21 +0200 Subject: [PATCH 7/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Oliver Baehler --- content/en/docs/resource-management/customquotas/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/en/docs/resource-management/customquotas/_index.md b/content/en/docs/resource-management/customquotas/_index.md index cca3ae3..3e1ec3a 100644 --- a/content/en/docs/resource-management/customquotas/_index.md +++ b/content/en/docs/resource-management/customquotas/_index.md @@ -131,7 +131,7 @@ The following constraints apply to the JSONPath: #### Matching Strategies -Implemententations how JSONPath expressions are evaluated and how their results are interpreted for conditional matching. +This section describes how JSONPath expressions are evaluated and how their results are interpreted for conditional matching. #### Truthy From 941fcba2ad59c72f7c68e80510996792983a1881 Mon Sep 17 00:00:00 2001 From: Sander Tervoert <32864332+sandert-k8s@users.noreply.github.com> Date: Tue, 9 Jun 2026 23:54:36 +0200 Subject: [PATCH 8/9] chore: fixes in quota docs (#93) * fix: apiVersion in docs Signed-off-by: sandert-k8s * chore: fix lb path Signed-off-by: sandert-k8s --------- Signed-off-by: sandert-k8s Signed-off-by: Oliver Baehler --- .../resource-management/customquotas/_index.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/content/en/docs/resource-management/customquotas/_index.md b/content/en/docs/resource-management/customquotas/_index.md index 3e1ec3a..0ce2663 100644 --- a/content/en/docs/resource-management/customquotas/_index.md +++ b/content/en/docs/resource-management/customquotas/_index.md @@ -245,10 +245,9 @@ spec: - matchLabels: capsule.clastix.io/tenant: solar sources: - - group: "" + - apiVersion: v1 kind: Pod op: count - version: v1 --- apiVersion: capsule.clastix.io/v1beta2 kind: CustomQuota @@ -258,10 +257,9 @@ metadata: spec: limit: 3 sources: - - group: "" + - apiVersion: v1 kind: Pod op: count - version: v1 ``` When we now try to create 6 `Pods` in the namespace `solar-test`, we can observe that the `GlobalCustomQuota` allows only 6 Pods in total across all namespaces of the tenant, while the `CustomQuota` allows only 3 Pods in the `solar-test` namespace: @@ -293,7 +291,7 @@ We can see that requests are blocked because of the limits by the `CustomQuota` A quota may define one or many sources. Each source describes: - * which objects are candidates (`group`, `version`, `kind`) + * which objects are candidates (`apiVersion`, `kind`) * what value is extracted from them ([`path`](#path), if applicable) * how that value contributes to usage ([`op`](#operations)) * optional additional source-level selectors @@ -306,11 +304,10 @@ Sources are evaluated independently and then aggregated into one total. ### GVK -Each source must identify a Kubernetes resource type by Group / Version / Kind. Example for a core Kubernetes Pod: +Each source must identify a Kubernetes resource type by apiVersion / Kind. Example for a core Kubernetes Pod: ```yaml -group: "" -version: v1 +apiVersion: v1 kind: Pod ``` @@ -710,7 +707,7 @@ spec: op: count selectors: - fieldSelectors: - - '.spec.type[?(@=="LoadBalancer")]' + - '.spec.type=="LoadBalancer"' ``` #### Aggregate ephemeral and persistent storage @@ -997,7 +994,7 @@ spec: op: count selectors: - fieldSelectors: - - '.spec.type[?(@=="LoadBalancer")]' + - '.spec.type=="LoadBalancer"' ``` #### Limit total memory requests of Pods in one namespace From 5d3d7d54e2b1c2da48ad9337a602f9e03640f420 Mon Sep 17 00:00:00 2001 From: Oliver Baehler Date: Wed, 10 Jun 2026 00:25:08 +0200 Subject: [PATCH 9/9] fix: improve rules api Signed-off-by: Oliver Baehler --- content/en/docs/reference.md | 144 +++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 22 deletions(-) diff --git a/content/en/docs/reference.md b/content/en/docs/reference.md index 6687396..8da1bc1 100644 --- a/content/en/docs/reference.md +++ b/content/en/docs/reference.md @@ -2053,10 +2053,25 @@ Enforcement for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **[registries](#rulestatusspecindexenforceregistriesindex)** | []object | Define registries which are allowed to be used within this tenant
The rules are aggregated, since you can use Regular Expressions the match registry endpoints | false | +| **action** | enum | Declare the action being performed on the enforcement rule:
deny: On match, deny admission request
allow: On match, allowed admission request
audit: On match, audit (post event) of admission request
*Enum*: allow, deny, audit
*Default*: deny
| false | +| **[workloads](#rulestatusspecindexenforceworkloads)** | object | Enforcement for Workloads (Pods) | false | -### RuleStatus.spec[index].enforce.registries[index] +### RuleStatus.spec[index].enforce.workloads + + + +Enforcement for Workloads (Pods) + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **qosClasses** | []string | Define Pod QoS classes matched by this enforcement rule.
Supported values are Guaranteed, Burstable and BestEffort. | false | +| **[registries](#rulestatusspecindexenforceworkloadsregistriesindex)** | []object | Define registries which are allowed to be used within this tenant
The rules are aggregated, since you can use Regular Expressions the match registry endpoints | false | +| **targets** | []enum | Define the enforcement targets this rule applies to.
If empty, each webhook applies its own backwards-compatible default.
*Enum*: pod/initcontainers, pod/ephemeralcontainers, pod/containers, pod/volumes
| false | + + +### RuleStatus.spec[index].enforce.workloads.registries[index] @@ -2065,9 +2080,9 @@ Enforcement for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **url** | string | OCI Registry endpoint, is treated as regular expression. | true | +| **exp** | string | Expression used to evaluate regex | false | +| **negate** | boolean | Negate regular Expression
*Default*: false
| false | | **policy** | []string | Allowed PullPolicy for the given registry. Supplying no value allows all policies. | false | -| **validation** | []enum | Requesting Resources
*Enum*: pod/images, pod/volumes
*Default*: [pod/images pod/volumes]
| false | ### RuleStatus.status @@ -2081,7 +2096,8 @@ RuleStatus contains the accumulated rules applying to namespace it's deployed in | :---- | :---- | :----------- | :-------- | | **[conditions](#rulestatusstatusconditionsindex)** | []object | Conditions | true | | **observedGeneration** | integer | ObservedGeneration is the most recent generation the controller has observed.
*Format*: int64
| false | -| **[rule](#rulestatusstatusrule)** | object | Managed Enforcement properties per Namespace (aggregated from rules) | false | +| **[rule](#rulestatusstatusrule)** | object | Deprecated: use Rules.
Rule contains a legacy flattened view and cannot fully represent action-aware rules.
| false | +| **[rules](#rulestatusstatusrulesindex)** | []object | Rules contains the effective namespace rules after tenant rule selection.
Order is preserved from the originating Tenant rules. | false | ### RuleStatus.status.conditions[index] @@ -2105,7 +2121,8 @@ Condition contains details for one aspect of the current state of this API Resou -Managed Enforcement properties per Namespace (aggregated from rules) +Deprecated: use Rules. +Rule contains a legacy flattened view and cannot fully represent action-aware rules. | **Name** | **Type** | **Description** | **Required** | @@ -2122,10 +2139,25 @@ Enforcement for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **[registries](#rulestatusstatusruleenforceregistriesindex)** | []object | Define registries which are allowed to be used within this tenant
The rules are aggregated, since you can use Regular Expressions the match registry endpoints | false | +| **action** | enum | Declare the action being performed on the enforcement rule:
deny: On match, deny admission request
allow: On match, allowed admission request
audit: On match, audit (post event) of admission request
*Enum*: allow, deny, audit
*Default*: deny
| false | +| **[workloads](#rulestatusstatusruleenforceworkloads)** | object | Enforcement for Workloads (Pods) | false | + + +### RuleStatus.status.rule.enforce.workloads + + + +Enforcement for Workloads (Pods) + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **qosClasses** | []string | Define Pod QoS classes matched by this enforcement rule.
Supported values are Guaranteed, Burstable and BestEffort. | false | +| **[registries](#rulestatusstatusruleenforceworkloadsregistriesindex)** | []object | Define registries which are allowed to be used within this tenant
The rules are aggregated, since you can use Regular Expressions the match registry endpoints | false | +| **targets** | []enum | Define the enforcement targets this rule applies to.
If empty, each webhook applies its own backwards-compatible default.
*Enum*: pod/initcontainers, pod/ephemeralcontainers, pod/containers, pod/volumes
| false | -### RuleStatus.status.rule.enforce.registries[index] +### RuleStatus.status.rule.enforce.workloads.registries[index] @@ -2134,9 +2166,62 @@ Enforcement for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **url** | string | OCI Registry endpoint, is treated as regular expression. | true | +| **exp** | string | Expression used to evaluate regex | false | +| **negate** | boolean | Negate regular Expression
*Default*: false
| false | +| **policy** | []string | Allowed PullPolicy for the given registry. Supplying no value allows all policies. | false | + + +### RuleStatus.status.rules[index] + + + +For future implementation where users might manage RuleStatus CRs themselves + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **[enforce](#rulestatusstatusrulesindexenforce)** | object | Enforcement for given rule | false | + + +### RuleStatus.status.rules[index].enforce + + + +Enforcement for given rule + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **action** | enum | Declare the action being performed on the enforcement rule:
deny: On match, deny admission request
allow: On match, allowed admission request
audit: On match, audit (post event) of admission request
*Enum*: allow, deny, audit
*Default*: deny
| false | +| **[workloads](#rulestatusstatusrulesindexenforceworkloads)** | object | Enforcement for Workloads (Pods) | false | + + +### RuleStatus.status.rules[index].enforce.workloads + + + +Enforcement for Workloads (Pods) + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **qosClasses** | []string | Define Pod QoS classes matched by this enforcement rule.
Supported values are Guaranteed, Burstable and BestEffort. | false | +| **[registries](#rulestatusstatusrulesindexenforceworkloadsregistriesindex)** | []object | Define registries which are allowed to be used within this tenant
The rules are aggregated, since you can use Regular Expressions the match registry endpoints | false | +| **targets** | []enum | Define the enforcement targets this rule applies to.
If empty, each webhook applies its own backwards-compatible default.
*Enum*: pod/initcontainers, pod/ephemeralcontainers, pod/containers, pod/volumes
| false | + + +### RuleStatus.status.rules[index].enforce.workloads.registries[index] + + + + + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **exp** | string | Expression used to evaluate regex | false | +| **negate** | boolean | Negate regular Expression
*Default*: false
| false | | **policy** | []string | Allowed PullPolicy for the given registry. Supplying no value allows all policies. | false | -| **validation** | []enum | Requesting Resources
*Enum*: pod/images, pod/volumes
*Default*: [pod/images pod/volumes]
| false | ## TenantOwner @@ -3449,10 +3534,25 @@ Enforcement for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **[registries](#tenantspecrulesindexenforceregistriesindex)** | []object | Define registries which are allowed to be used within this tenant
The rules are aggregated, since you can use Regular Expressions the match registry endpoints | false | +| **action** | enum | Declare the action being performed on the enforcement rule:
deny: On match, deny admission request
allow: On match, allowed admission request
audit: On match, audit (post event) of admission request
*Enum*: allow, deny, audit
*Default*: deny
| false | +| **[workloads](#tenantspecrulesindexenforceworkloads)** | object | Enforcement for Workloads (Pods) | false | + + +### Tenant.spec.rules[index].enforce.workloads + + + +Enforcement for Workloads (Pods) + + +| **Name** | **Type** | **Description** | **Required** | +| :---- | :---- | :----------- | :-------- | +| **qosClasses** | []string | Define Pod QoS classes matched by this enforcement rule.
Supported values are Guaranteed, Burstable and BestEffort. | false | +| **[registries](#tenantspecrulesindexenforceworkloadsregistriesindex)** | []object | Define registries which are allowed to be used within this tenant
The rules are aggregated, since you can use Regular Expressions the match registry endpoints | false | +| **targets** | []enum | Define the enforcement targets this rule applies to.
If empty, each webhook applies its own backwards-compatible default.
*Enum*: pod/initcontainers, pod/ephemeralcontainers, pod/containers, pod/volumes
| false | -### Tenant.spec.rules[index].enforce.registries[index] +### Tenant.spec.rules[index].enforce.workloads.registries[index] @@ -3461,9 +3561,9 @@ Enforcement for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **url** | string | OCI Registry endpoint, is treated as regular expression. | true | +| **exp** | string | Expression used to evaluate regex | false | +| **negate** | boolean | Negate regular Expression
*Default*: false
| false | | **policy** | []string | Allowed PullPolicy for the given registry. Supplying no value allows all policies. | false | -| **validation** | []enum | Requesting Resources
*Enum*: pod/images, pod/volumes
*Default*: [pod/images pod/volumes]
| false | ### Tenant.spec.rules[index].namespaceSelector @@ -3503,10 +3603,10 @@ Permissions for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **[rules](#tenantspecrulesindexpermissionsrulesindex)** | []object | Define Promotion Rules which distributed additional ClusterRoles across the Tenant
for promoted ServiceAccounts. | false | +| **[promotions](#tenantspecrulesindexpermissionspromotionsindex)** | []object | Define Promotion Rules which distributed additional ClusterRoles across the Tenant
for promoted ServiceAccounts. | false | -### Tenant.spec.rules[index].permissions.rules[index] +### Tenant.spec.rules[index].permissions.promotions[index] @@ -3516,10 +3616,10 @@ Permissions for given rule | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | | **clusterRoles** | []string | ClusterRoles granted to the promoted ServiceAccounts across the Tenant
kubebuilder:validation:Minimum=1 | false | -| **[selector](#tenantspecrulesindexpermissionsrulesindexselector)** | object | Match ServiceAccounts which are promoted which are granted these additional ClusterRoles
across the Tenant | false | +| **[selector](#tenantspecrulesindexpermissionspromotionsindexselector)** | object | Match ServiceAccounts which are promoted which are granted these additional ClusterRoles
across the Tenant | false | -### Tenant.spec.rules[index].permissions.rules[index].selector +### Tenant.spec.rules[index].permissions.promotions[index].selector @@ -3529,11 +3629,11 @@ across the Tenant | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **[matchExpressions](#tenantspecrulesindexpermissionsrulesindexselectormatchexpressionsindex)** | []object | matchExpressions is a list of label selector requirements. The requirements are ANDed. | false | +| **[matchExpressions](#tenantspecrulesindexpermissionspromotionsindexselectormatchexpressionsindex)** | []object | matchExpressions is a list of label selector requirements. The requirements are ANDed. | false | | **matchLabels** | map[string]string | matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
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. | false | -### Tenant.spec.rules[index].permissions.rules[index].selector.matchExpressions[index] +### Tenant.spec.rules[index].permissions.promotions[index].selector.matchExpressions[index] @@ -3832,9 +3932,9 @@ Managed Metadata | **Name** | **Type** | **Description** | **Required** | | :---- | :---- | :----------- | :-------- | -| **url** | string | OCI Registry endpoint, is treated as regular expression. | true | +| **exp** | string | Expression used to evaluate regex | false | +| **negate** | boolean | Negate regular Expression
*Default*: false
| false | | **policy** | []string | Allowed PullPolicy for the given registry. Supplying no value allows all policies. | false | -| **validation** | []enum | Requesting Resources
*Enum*: pod/images, pod/volumes
*Default*: [pod/images pod/volumes]
| false | ### Tenant.status.spaces[index].metadata