feat: kubeconfig generator (#933)

* feat(api): kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* refactor: abstracting enqueue to channel function

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* fix: avoiding multiple context registration

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat: kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(helm): deployment for kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
This commit is contained in:
Dario Tranchitella
2025-09-22 15:32:50 +02:00
committed by GitHub
parent 4bace03fc3
commit cb2152d5a7
22 changed files with 2108 additions and 15 deletions

View File

@@ -0,0 +1,114 @@
# Kubeconfig Generator
The **Kubeconfig Generator** is a Kamaji extension that simplifies the distribution of Kubeconfig files for tenant clusters managed through Kamaji.
Instead of manually exporting and editing credentials, the generator automates the creation of kubeconfigs aligned with your organizational policies.
## Motivation
When managing multiple Tenant Control Planes (TCPs), cluster administrators often face two challenges:
1. **Consistency**: ensuring kubeconfigs are generated with the correct user identity, groups, and endpoints
2. **Scalability**: distributing kubeconfigs to users across potentially dozens of tenant clusters without manual steps
The `KubeconfigGenerator` resource addresses these problems by:
- Selecting which TCPs to target via label selectors.
- Defining how to build user and group identities in kubeconfigs.
- Automatically maintaining kubeconfigs as new tenant clusters are created or updated.
This provides a single, declarative way to manage kubeconfig lifecycle across all your tenants,
especially convenient if those cases where an Identity Provider can't be used to delegate access to Tenant Control Plane clusters.
## How it Works
### Selection
- `namespaceSelector` filters the namespaces from which Tenant Control Planes are discovered.
- `tenantControlPlaneSelector` further refines which TCPs to include.
### Identity Definition
The `user` and `groups` fields use compound values, which can be either:
- A static string (e.g., `developer`)
- A dynamic reference resolved from the TCP object (e.g., `metadata.name`)
This allows kubeconfigs to be tailored to the clusters context or a fixed organizational pattern.
### Endpoint Resolution
The generator pulls the API server endpoint from the TCPs `admin` kubeconfig.
By default it uses the `admin.svc` template, but this can be overridden with the `controlPlaneEndpointFrom` field.
### Status and Errors
The resource keeps track of how many kubeconfigs were attempted, how many succeeded,
and provides detailed error reports for failed generations.
## Typical Use Cases
- **Platform Operators**: automatically distribute kubeconfigs to developers as new tenant clusters are provisioned.
- **Multi-team Environments**: ensure each team gets kubeconfigs with the correct groups for RBAC authorization.
- **Least Privilege Principle**: avoid distributing `cluster-admin` credentials with a fine-grained RBAC
- **Dynamic Access**: use `fromDefinition` references to bind kubeconfig identities directly to tenant metadata
(e.g., prefixing users with the TCP's name).
## Example Scenario
A SaaS provider runs multiple Tenant Control Planes, each corresponding to a different customer.
Instead of manually managing kubeconfigs for every customer environment, the operator defines a single `KubeconfigGenerator`:
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: KubeconfigGenerator
metadata:
name: tenant
spec:
# Select only Tenant Control Planes living in namespaces
# labeled as production environments
namespaceSelector:
matchLabels:
environment: production
# Match all Tenant Control Planes in those namespaces
tenantControlPlaneSelector: {}
# Assign a static group "customer-admins"
groups:
- stringValue: "customer-admins"
# Derive the user identity dynamically from the TenantControlPlane metadata
user:
fromDefinition: "metadata.name"
# Use the public admin endpoint from the TCPs kubeconfig
controlPlaneEndpointFrom: "admin.conf"
```
- Matches all TCPs in namespaces labeled `environment=production`.
- Generates kubeconfigs with group `customer-admins`.
- Derives the user identity from the TCPs `metadata.name`.
As new tenants are created, their kubeconfigs are generated automatically and kept up to date.
```
$: kubectl get secret --all-namespaces -l kamaji.clastix.io/managed-by=tenant
NAMESPACE NAME TYPE DATA AGE
alpha-tnt env-133-tenant Opaque 1 12h
alpha-tnt env-130-tenant Opaque 1 2d
bravo-tnt prod-tenant Opaque 1 2h
charlie-tnt stable-tenant Opaque 1 1d
```
## Observability
The generator exposes its status directly in the CRD:
- `resources`: total number of TCPs targeted.
- `availableResources`: successfully generated kubeconfigs.
- `errors`: list of failed kubeconfig generations, including the affected resource and error message.
This allows quick debugging and operational awareness.
## Deployment
The _Kubeconfig Generator_ is **not** enabled by default since it's still in experimental state.
It can be enabled using the Helm value `kubeconfigGenerator.enabled=true` which is defaulted to `false`.

View File

@@ -27271,6 +27271,8 @@ Resource Types:
- [DataStore](#datastore)
- [KubeconfigGenerator](#kubeconfiggenerator)
- [TenantControlPlane](#tenantcontrolplane)
@@ -27985,6 +27987,415 @@ DataStoreStatus defines the observed state of DataStore.
</tr></tbody>
</table>
### KubeconfigGenerator
KubeconfigGenerator is the Schema for the kubeconfiggenerators API.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>apiVersion</b></td>
<td>string</td>
<td>kamaji.clastix.io/v1alpha1</td>
<td>true</td>
</tr>
<tr>
<td><b>kind</b></td>
<td>string</td>
<td>KubeconfigGenerator</td>
<td>true</td>
</tr>
<tr>
<td><b><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#objectmeta-v1-meta">metadata</a></b></td>
<td>object</td>
<td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>
<td>true</td>
</tr><tr>
<td><b><a href="#kubeconfiggeneratorspec">spec</a></b></td>
<td>object</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#kubeconfiggeneratorstatus">status</a></b></td>
<td>object</td>
<td>
KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorspec">`KubeconfigGenerator.spec`</span>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b><a href="#kubeconfiggeneratorspecuser">user</a></b></td>
<td>object</td>
<td>
User resolves to a string to identify the client, assigned to the x509 Common Name field.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>controlPlaneEndpointFrom</b></td>
<td>string</td>
<td>
ControlPlaneEndpointFrom is the key used to extract the Tenant Control Plane endpoint that must be used by the generator.
The targeted Secret is the `${TCP}-admin-kubeconfig` one, default to `admin.svc`.<br/>
<br/>
<i>Default</i>: admin.svc<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#kubeconfiggeneratorspecgroupsindex">groups</a></b></td>
<td>[]object</td>
<td>
Groups is resolved a set of strings used to assign the x509 organisations field.
It will be recognised by Kubernetes as user groups.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#kubeconfiggeneratorspecnamespaceselector">namespaceSelector</a></b></td>
<td>object</td>
<td>
NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#kubeconfiggeneratorspectenantcontrolplaneselector">tenantControlPlaneSelector</a></b></td>
<td>object</td>
<td>
TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorspecuser">`KubeconfigGenerator.spec.user`</span>
User resolves to a string to identify the client, assigned to the x509 Common Name field.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>fromDefinition</b></td>
<td>string</td>
<td>
FromDefinition is used to generate a dynamic value,
it uses the dot notation to access fields from the referenced TenantControlPlane object:
e.g.: metadata.name<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>stringValue</b></td>
<td>string</td>
<td>
StringValue is a static string value.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorspecgroupsindex">`KubeconfigGenerator.spec.groups[index]`</span>
CompoundValue allows defining a static, or a dynamic value.
Options are mutually exclusive, just one should be picked up.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>fromDefinition</b></td>
<td>string</td>
<td>
FromDefinition is used to generate a dynamic value,
it uses the dot notation to access fields from the referenced TenantControlPlane object:
e.g.: metadata.name<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>stringValue</b></td>
<td>string</td>
<td>
StringValue is a static string value.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorspecnamespaceselector">`KubeconfigGenerator.spec.namespaceSelector`</span>
NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b><a href="#kubeconfiggeneratorspecnamespaceselectormatchexpressionsindex">matchExpressions</a></b></td>
<td>[]object</td>
<td>
matchExpressions is a list of label selector requirements. The requirements are ANDed.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>matchLabels</b></td>
<td>map[string]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorspecnamespaceselectormatchexpressionsindex">`KubeconfigGenerator.spec.namespaceSelector.matchExpressions[index]`</span>
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>key</b></td>
<td>string</td>
<td>
key is the label key that the selector applies to.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>operator</b></td>
<td>string</td>
<td>
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>values</b></td>
<td>[]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorspectenantcontrolplaneselector">`KubeconfigGenerator.spec.tenantControlPlaneSelector`</span>
TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b><a href="#kubeconfiggeneratorspectenantcontrolplaneselectormatchexpressionsindex">matchExpressions</a></b></td>
<td>[]object</td>
<td>
matchExpressions is a list of label selector requirements. The requirements are ANDed.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>matchLabels</b></td>
<td>map[string]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorspectenantcontrolplaneselectormatchexpressionsindex">`KubeconfigGenerator.spec.tenantControlPlaneSelector.matchExpressions[index]`</span>
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>key</b></td>
<td>string</td>
<td>
key is the label key that the selector applies to.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>operator</b></td>
<td>string</td>
<td>
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>values</b></td>
<td>[]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorstatus">`KubeconfigGenerator.status`</span>
KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>availableResources</b></td>
<td>integer</td>
<td>
AvailableResources is the sum of successfully generated resources.
In case of a different value compared to Resources, check the field errors.<br/>
<br/>
<i>Default</i>: 0<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>resources</b></td>
<td>integer</td>
<td>
Resources is the sum of targeted TenantControlPlane objects.<br/>
<br/>
<i>Default</i>: 0<br/>
</td>
<td>true</td>
</tr><tr>
<td><b><a href="#kubeconfiggeneratorstatuserrorsindex">errors</a></b></td>
<td>[]object</td>
<td>
Errors is the list of failed kubeconfig generations.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kubeconfiggeneratorstatuserrorsindex">`KubeconfigGenerator.status.errors[index]`</span>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>message</b></td>
<td>string</td>
<td>
Message is the error message recorded upon the last generator run.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>resource</b></td>
<td>string</td>
<td>
Resource is the Namespaced name of the errored resource.<br/>
</td>
<td>true</td>
</tr></tbody>
</table>
### TenantControlPlane

View File

@@ -77,6 +77,7 @@ nav:
- guides/datastore-migration.md
- guides/gitops.md
- guides/console.md
- guides/kubeconfig-generator.md
- guides/upgrade.md
- guides/monitoring.md
- guides/terraform.md