Compare commits

..

18 Commits

Author SHA1 Message Date
Dario Tranchitella
081b4c72b3 feat: additional service ports (#999)
* feat: additional service ports

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

* docs: additional service ports

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

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-10-30 13:22:15 +01:00
dependabot[bot]
cb8086754b feat(deps): bump github.com/onsi/ginkgo/v2 from 2.27.1 to 2.27.2 (#998)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.27.1 to 2.27.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.27.1...v2.27.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-28 08:58:52 +01:00
dependabot[bot]
34c02a96f6 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.26.0 to 2.27.1 (#995)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.26.0 to 2.27.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.26.0...v2.27.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.27.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 10:37:44 +01:00
TAnderson
f29a10ba5f correction (#996)
Co-authored-by: Johannes Ibald <johannes.ibald@etes.de>
2025-10-23 14:49:45 +02:00
athena967
0656dbb803 Netalia vendor (#994)
* Update ADOPTERS.md with Netalia as vendor.
2025-10-23 10:31:59 +02:00
Adriano Pezzuto
99fb71ecf9 fix(helm): use 0.0.0+latest for kamaji-crd (#982)
Co-authored-by: bsctl <bsctl@clastix.io>
2025-10-18 16:01:24 +02:00
Adriano Pezzuto
da180c9ce0 feat(docs): document how to install helm kamaji-crds (#983)
* feat(docs): document how to install helm kamaji-crds

* fix(docs): document how to install helm kamaji-crds

---------

Co-authored-by: bsctl <bsctl@clastix.io>
2025-10-18 16:01:04 +02:00
dependabot[bot]
4cced97991 feat(deps): bump github.com/docker/docker (#985)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.5.0+incompatible to 28.5.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.5.0...v28.5.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.5.1+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 16:00:17 +02:00
dependabot[bot]
a00e4544f9 feat(deps): bump sigs.k8s.io/controller-runtime from 0.22.1 to 0.22.3 (#988)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.1 to 0.22.3.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.1...v0.22.3)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-version: 0.22.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 16:00:05 +02:00
dependabot[bot]
b1984dc66d feat(deps): bump github.com/nats-io/nats.go from 1.46.1 to 1.47.0 (#989)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.1 to 1.47.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.46.1...v1.47.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 15:59:45 +02:00
Timofei Larkin
8d25078c47 feat: don't clobber 3rd-party labels (#992)
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-17 17:35:20 +02:00
dependabot[bot]
bc85d8b73c feat(deps): bump github.com/docker/docker (#978)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.4.0+incompatible to 28.5.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.4.0...v28.5.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.5.0+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 14:12:20 +02:00
Dario Tranchitella
de459fb5da feat!: write permissions (#937)
* fix: decoding object only if requested

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

* feat(api): limiting write permissions

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

* feat: write permissions handlers, routes, and controller

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

* docs: write permissions

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

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-10-03 14:30:58 +02:00
dependabot[bot]
2b707423ff feat(deps): bump github.com/onsi/ginkgo/v2 from 2.25.3 to 2.26.0 (#977)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.25.3 to 2.26.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.25.3...v2.26.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-03 14:02:10 +02:00
Loïc Brun
285cef0f02 fix(konnectivity): rotate certicate during certificate authority rotation (#976) 2025-10-02 16:17:59 +02:00
dependabot[bot]
f6686f6efa feat(deps): bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1 (#973)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.0 to 1.46.1.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.46.0...v1.46.1)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.46.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 14:55:52 +02:00
Dario Tranchitella
2a809a79c4 docs(readme): managed kubernetes on hetzner article (#974) 2025-10-01 21:20:06 +02:00
Dario Tranchitella
f477df2a84 docs(readme): minikube medium article (#972) 2025-09-30 15:46:02 +02:00
39 changed files with 981 additions and 69 deletions

View File

@@ -14,6 +14,7 @@ Feel free to open a Pull-Request to get yours listed.
| Vendor | DCloud | 2024 | [link](https://dcloud.co.id) | DCloud is an Indonesian Cloud Provider using Kamaji to build and offer [Managed Kubernetes Service](https://dcloud.co.id/dkubes.html). |
| Vendor | Dinova | 2025 | [link](https://dinova.one/) | Dinova is an Italian cloud services provider that integrates Kamaji in its datacenters to offer fully managed Kubernetes clusters. |
| End-user | KINX | 2024 | [link](https://kinx.net/?lang=en) | KINX is an Internet infrastructure service provider and will use kamaji for its new [Managed Kubernetes Service](https://kinx.net/service/cloud/kubernetes/intro/?lang=en). |
| Vendor | Netalia | 2025 | [link](https://www.netalia.it) | Netalia uses Kamaji for the Italian cloud
| Vendor | Netsons | 2023 | [link](https://www.netsons.com) | Netsons is an Italian hosting and cloud provider and uses Kamaji in its [Managed Kubernetes](https://www.netsons.com/kubernetes) offering. |
| Vendor | NVIDIA | 2024 | [link](https://github.com/NVIDIA/doca-platform) | DOCA Platform Framework manages provisioning and service orchestration for NVIDIA Bluefield DPUs. |
| R&D | Orange | 2024 | [link](https://gitlab.com/Orange-OpenSource/kanod) | Orange is a French telecommunications company using Kamaji for experimental research purpose, with Kanod research solution. |

View File

@@ -123,6 +123,8 @@ Since Kamaji is just focusing on the Control Plane a [Kamaji's Cluster API Contr
- YouTube ▶️ [Rancher & Kamaji: solving multitenancy challenges in the Kubernetes world](https://www.youtube.com/watch?v=VXHNrMmlF8U)
- YouTube ▶️ [Enabling Self-Service Kubernetes clusters with Kamaji and Paralus](https://www.youtube.com/watch?v=JWA2LwZazM0)
- YouTube ▶️ [Hosted Control Plane on Kubernetes (HPC) with Kamaji and K0mostron by Hervé Leclerc, ALTER WAY](https://www.youtube.com/watch?v=vmRdE2ngn78)
- Medium 📖 [Set up Virtual Control Planes with Kamaji on Minikube, by Ben Soer](https://medium.com/@bensoer/set-up-virtual-control-planes-with-kamaji-on-minikube-a540be0275aa)
- Hands-On tutorial 📖 [How to build your own managed Kubernetes service on Hetzner Cloud, by Hans Jörg Wieland](https://wieland.tech/blog/kamaji-cluster-api-and-etcd)
### 🏷️ Versioning

View File

@@ -189,12 +189,14 @@ type KubernetesStatus struct {
Ingress *KubernetesIngressStatus `json:"ingress,omitempty"`
}
// +kubebuilder:validation:Enum=Provisioning;CertificateAuthorityRotating;Upgrading;Migrating;Ready;NotReady;Sleeping
// +kubebuilder:validation:Enum=Unknown;Provisioning;CertificateAuthorityRotating;Upgrading;Migrating;Ready;NotReady;Sleeping;WriteLimited
type KubernetesVersionStatus string
var (
VersionUnknown KubernetesVersionStatus = "Unknown"
VersionProvisioning KubernetesVersionStatus = "Provisioning"
VersionSleeping KubernetesVersionStatus = "Sleeping"
VersionWriteLimited KubernetesVersionStatus = "WriteLimited"
VersionCARotating KubernetesVersionStatus = "CertificateAuthorityRotating"
VersionUpgrading KubernetesVersionStatus = "Upgrading"
VersionMigrating KubernetesVersionStatus = "Migrating"

View File

@@ -7,6 +7,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// NetworkProfileSpec defines the desired state of NetworkProfile.
@@ -89,6 +90,32 @@ type KubernetesSpec struct {
AdmissionControllers AdmissionControllers `json:"admissionControllers,omitempty"`
}
type AdditionalPort struct {
// The name of this port within the Service created by Kamaji.
// This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`.
Name string `json:"name"`
// The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
//+kubebuilder:validation:Enum=TCP;UDP;SCTP
//+kubebuilder:default=TCP
Protocol corev1.Protocol `json:"protocol,omitempty"`
// The application protocol for this port.
// This is used as a hint for implementations to offer richer behavior for protocols that they understand.
// This field follows standard Kubernetes label syntax.
// Valid values are either:
//
// * Un-prefixed protocol names - reserved for IANA standard service names (as per
// RFC-6335 and https://www.iana.org/assignments/service-names).
AppProtocol *string `json:"appProtocol,omitempty"`
// The port that will be exposed by this service.
Port int32 `json:"port"`
// Number or name of the port to access on the pods of the Tenant Control Plane.
// Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.
// If this is a string, it will be looked up as a named port in the
// target Pod's container ports. If this is not specified, the value
// of the 'port' field is used (an identity map).
TargetPort intstr.IntOrString `json:"targetPort"`
}
// AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
type AdditionalMetadata struct {
Labels map[string]string `json:"labels,omitempty"`
@@ -198,6 +225,9 @@ type ControlPlaneExtraArgs struct {
type ServiceSpec struct {
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
// AdditionalPorts allows adding additional ports to the Service generated Kamaji
// which targets the Tenant Control Plane pods.
AdditionalPorts []AdditionalPort `json:"additionalPorts,omitempty"`
// ServiceType allows specifying how to expose the Tenant Control Plane.
ServiceType ServiceType `json:"serviceType"`
}
@@ -297,6 +327,20 @@ type AddonsSpec struct {
KubeProxy *AddonSpec `json:"kubeProxy,omitempty"`
}
type Permissions struct {
BlockCreate bool `json:"blockCreation,omitempty"`
BlockUpdate bool `json:"blockUpdate,omitempty"`
BlockDelete bool `json:"blockDeletion,omitempty"`
}
func (p *Permissions) HasAnyLimitation() bool {
if p.BlockCreate || p.BlockUpdate || p.BlockDelete {
return true
}
return false
}
// TenantControlPlaneSpec defines the desired state of TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStore) || has(self.dataStore)", message="unsetting the dataStore is not supported"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreSchema) || has(self.dataStoreSchema)", message="unsetting the dataStoreSchema is not supported"
@@ -306,6 +350,13 @@ type AddonsSpec struct {
// +kubebuilder:validation:XValidation:rule="self.controlPlane.service.serviceType != 'LoadBalancer' || (oldSelf.controlPlane.service.serviceType != 'LoadBalancer' && self.controlPlane.service.serviceType == 'LoadBalancer') || has(self.networkProfile.loadBalancerClass) == has(oldSelf.networkProfile.loadBalancerClass)",message="LoadBalancerClass cannot be set or unset at runtime"
type TenantControlPlaneSpec struct {
// WritePermissions allows to select which operations (create, delete, update) must be blocked:
// by default, all actions are allowed, and API Server can write to its Datastore.
//
// By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
// this phase can be used to prevent Datastore quota exhaustion or for your own business logic
// (e.g.: blocking creation and update, but allowing deletion to "clean up" space).
WritePermissions Permissions `json:"writePermissions,omitempty"`
// DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
// When Kamaji runs with the default DataStore flag, all empty values will inherit the default value.
// By leaving it empty and running Kamaji with no default DataStore flag, it is possible to achieve automatic assignment to a specific DataStore object.

View File

@@ -57,6 +57,27 @@ func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdditionalPort) DeepCopyInto(out *AdditionalPort) {
*out = *in
if in.AppProtocol != nil {
in, out := &in.AppProtocol, &out.AppProtocol
*out = new(string)
**out = **in
}
out.TargetPort = in.TargetPort
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalPort.
func (in *AdditionalPort) DeepCopy() *AdditionalPort {
if in == nil {
return nil
}
out := new(AdditionalPort)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdditionalVolumeMounts) DeepCopyInto(out *AdditionalVolumeMounts) {
*out = *in
@@ -1285,6 +1306,21 @@ func (in *NetworkProfileSpec) DeepCopy() *NetworkProfileSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Permissions) DeepCopyInto(out *Permissions) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permissions.
func (in *Permissions) DeepCopy() *Permissions {
if in == nil {
return nil
}
out := new(Permissions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PublicKeyPrivateKeyPairStatus) DeepCopyInto(out *PublicKeyPrivateKeyPairStatus) {
*out = *in
@@ -1336,6 +1372,13 @@ func (in *SecretReference) DeepCopy() *SecretReference {
func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
*out = *in
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
if in.AdditionalPorts != nil {
in, out := &in.AdditionalPorts, &out.AdditionalPorts
*out = make([]AdditionalPort, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec.
@@ -1449,6 +1492,7 @@ func (in *TenantControlPlaneList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantControlPlaneSpec) DeepCopyInto(out *TenantControlPlaneSpec) {
*out = *in
out.WritePermissions = in.WritePermissions
in.ControlPlane.DeepCopyInto(&out.ControlPlane)
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
in.NetworkProfile.DeepCopyInto(&out.NetworkProfile)

View File

@@ -14,7 +14,7 @@ name: kamaji-crds
sources:
- https://github.com/clastix/kamaji
type: application
version: 0.0.0+edge
version: 0.0.0+latest
annotations:
artifacthub.io/crds: |
- kind: TenantControlPlane

View File

@@ -1,6 +1,6 @@
# kamaji-crds
![Version: 0.0.0+edge](https://img.shields.io/badge/Version-0.0.0+edge-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square)
![Version: 0.0.0+latest](https://img.shields.io/badge/Version-0.0.0+latest-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square)
Kamaji is the Hosted Control Plane Manager for Kubernetes.

View File

@@ -6738,6 +6738,56 @@ versions:
type: string
type: object
type: object
additionalPorts:
description: |-
AdditionalPorts allows adding additional ports to the Service generated Kamaji
which targets the Tenant Control Plane pods.
items:
properties:
appProtocol:
description: |-
The application protocol for this port.
This is used as a hint for implementations to offer richer behavior for protocols that they understand.
This field follows standard Kubernetes label syntax.
Valid values are either:
* Un-prefixed protocol names - reserved for IANA standard service names (as per
RFC-6335 and https://www.iana.org/assignments/service-names).
type: string
name:
description: |-
The name of this port within the Service created by Kamaji.
This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`.
type: string
port:
description: The port that will be exposed by this service.
format: int32
type: integer
protocol:
default: TCP
description: The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
enum:
- TCP
- UDP
- SCTP
type: string
targetPort:
anyOf:
- type: integer
- type: string
description: |-
Number or name of the port to access on the pods of the Tenant Control Plane.
Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.
If this is a string, it will be looked up as a named port in the
target Pod's container ports. If this is not specified, the value
of the 'port' field is used (an identity map).
x-kubernetes-int-or-string: true
required:
- name
- port
- targetPort
type: object
type: array
serviceType:
description: ServiceType allows specifying how to expose the Tenant Control Plane.
enum:
@@ -6955,6 +7005,22 @@ versions:
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
type: string
type: object
writePermissions:
description: |-
WritePermissions allows to select which operations (create, delete, update) must be blocked:
by default, all actions are allowed, and API Server can write to its Datastore.
By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
this phase can be used to prevent Datastore quota exhaustion or for your own business logic
(e.g.: blocking creation and update, but allowing deletion to "clean up" space).
properties:
blockCreation:
type: boolean
blockDeletion:
type: boolean
blockUpdate:
type: boolean
type: object
required:
- controlPlane
- kubernetes
@@ -7703,6 +7769,7 @@ versions:
default: Provisioning
description: Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.
enum:
- Unknown
- Provisioning
- CertificateAuthorityRotating
- Upgrading
@@ -7710,6 +7777,7 @@ versions:
- Ready
- NotReady
- Sleeping
- WriteLimited
type: string
version:
description: Version is the running Kubernetes version of the Tenant Control Plane.

View File

@@ -6746,6 +6746,56 @@ spec:
type: string
type: object
type: object
additionalPorts:
description: |-
AdditionalPorts allows adding additional ports to the Service generated Kamaji
which targets the Tenant Control Plane pods.
items:
properties:
appProtocol:
description: |-
The application protocol for this port.
This is used as a hint for implementations to offer richer behavior for protocols that they understand.
This field follows standard Kubernetes label syntax.
Valid values are either:
* Un-prefixed protocol names - reserved for IANA standard service names (as per
RFC-6335 and https://www.iana.org/assignments/service-names).
type: string
name:
description: |-
The name of this port within the Service created by Kamaji.
This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`.
type: string
port:
description: The port that will be exposed by this service.
format: int32
type: integer
protocol:
default: TCP
description: The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
enum:
- TCP
- UDP
- SCTP
type: string
targetPort:
anyOf:
- type: integer
- type: string
description: |-
Number or name of the port to access on the pods of the Tenant Control Plane.
Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.
If this is a string, it will be looked up as a named port in the
target Pod's container ports. If this is not specified, the value
of the 'port' field is used (an identity map).
x-kubernetes-int-or-string: true
required:
- name
- port
- targetPort
type: object
type: array
serviceType:
description: ServiceType allows specifying how to expose the Tenant Control Plane.
enum:
@@ -6963,6 +7013,22 @@ spec:
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
type: string
type: object
writePermissions:
description: |-
WritePermissions allows to select which operations (create, delete, update) must be blocked:
by default, all actions are allowed, and API Server can write to its Datastore.
By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
this phase can be used to prevent Datastore quota exhaustion or for your own business logic
(e.g.: blocking creation and update, but allowing deletion to "clean up" space).
properties:
blockCreation:
type: boolean
blockDeletion:
type: boolean
blockUpdate:
type: boolean
type: object
required:
- controlPlane
- kubernetes
@@ -7711,6 +7777,7 @@ spec:
default: Provisioning
description: Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.
enum:
- Unknown
- Provisioning
- CertificateAuthorityRotating
- Upgrading
@@ -7718,6 +7785,7 @@ spec:
- Ready
- NotReady
- Sleeping
- WriteLimited
type: string
version:
description: Version is the running Kubernetes version of the Tenant Control Plane.

View File

@@ -219,6 +219,9 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
routes.TenantControlPlaneMigrate{}: {
handlers.Freeze{},
},
routes.TenantControlPlaneWritePermission{}: {
handlers.WritePermission{},
},
routes.TenantControlPlaneDefaults{}: {
handlers.TenantControlPlaneDefaults{
DefaultDatastore: datastore,

View File

@@ -0,0 +1,207 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package controllers
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/util/errors"
"k8s.io/utils/ptr"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
sooterrors "github.com/clastix/kamaji/controllers/soot/controllers/errors"
"github.com/clastix/kamaji/controllers/utils"
"github.com/clastix/kamaji/internal/utilities"
)
type WritePermissions struct {
Logger logr.Logger
Client client.Client
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
WebhookNamespace string
WebhookServiceName string
WebhookCABundle []byte
TriggerChannel chan event.GenericEvent
}
func (r *WritePermissions) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
tcp, err := r.GetTenantControlPlaneFunc()
if err != nil {
if errors.Is(err, sooterrors.ErrPausedReconciliation) {
r.Logger.Info(err.Error())
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
// Cannot detect the status of the TenantControlPlane, enqueuing back
if tcp.Status.Kubernetes.Version.Status == nil {
return reconcile.Result{RequeueAfter: time.Second}, nil
}
switch {
case ptr.Deref(tcp.Status.Kubernetes.Version.Status, kamajiv1alpha1.VersionUnknown) == kamajiv1alpha1.VersionWriteLimited &&
tcp.Spec.WritePermissions.HasAnyLimitation():
err = r.createOrUpdate(ctx, tcp.Spec.WritePermissions)
default:
err = r.cleanup(ctx)
}
if err != nil {
r.Logger.Error(err, "reconciliation failed")
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
func (r *WritePermissions) createOrUpdate(ctx context.Context, writePermissions kamajiv1alpha1.Permissions) error {
obj := r.object().DeepCopy()
_, err := utilities.CreateOrUpdateWithConflict(ctx, r.Client, obj, func() error {
obj.Webhooks = []admissionregistrationv1.ValidatingWebhook{
{
Name: "leases.write-permissions.kamaji.clastix.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: ptr.To(fmt.Sprintf("https://%s.%s.svc:443/write-permission", r.WebhookServiceName, r.WebhookNamespace)),
CABundle: r.WebhookCABundle,
},
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Delete,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"*"},
APIVersions: []string{"*"},
Resources: []string{"*"},
Scope: ptr.To(admissionregistrationv1.NamespacedScope),
},
},
},
FailurePolicy: ptr.To(admissionregistrationv1.Fail),
MatchPolicy: ptr.To(admissionregistrationv1.Equivalent),
NamespaceSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "kubernetes.io/metadata.name",
Operator: metav1.LabelSelectorOpIn,
Values: []string{
"kube-node-lease",
},
},
},
},
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNoneOnDryRun),
AdmissionReviewVersions: []string{"v1"},
},
{
Name: "catchall.write-permissions.kamaji.clastix.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: ptr.To(fmt.Sprintf("https://%s.%s.svc:443/write-permission", r.WebhookServiceName, r.WebhookNamespace)),
CABundle: r.WebhookCABundle,
},
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: func() []admissionregistrationv1.OperationType {
var ops []admissionregistrationv1.OperationType
if writePermissions.BlockCreate {
ops = append(ops, admissionregistrationv1.Create)
}
if writePermissions.BlockUpdate {
ops = append(ops, admissionregistrationv1.Update)
}
if writePermissions.BlockDelete {
ops = append(ops, admissionregistrationv1.Delete)
}
return ops
}(),
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"*"},
APIVersions: []string{"*"},
Resources: []string{"*"},
Scope: ptr.To(admissionregistrationv1.AllScopes),
},
},
},
FailurePolicy: ptr.To(admissionregistrationv1.Fail),
MatchPolicy: ptr.To(admissionregistrationv1.Equivalent),
NamespaceSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "kubernetes.io/metadata.name",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{
"kube-system",
"kube-node-lease",
},
},
},
},
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNoneOnDryRun),
TimeoutSeconds: nil,
AdmissionReviewVersions: []string{"v1"},
},
}
return nil
})
return err
}
func (r *WritePermissions) cleanup(ctx context.Context) error {
if err := r.Client.Delete(ctx, r.object()); err != nil {
if apierrors.IsNotFound(err) {
return nil
}
return fmt.Errorf("unable to clean-up ValidationWebhook required for write permissions: %w", err)
}
return nil
}
func (r *WritePermissions) SetupWithManager(mgr manager.Manager) error {
r.TriggerChannel = make(chan event.GenericEvent)
return controllerruntime.NewControllerManagedBy(mgr).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
For(&admissionregistrationv1.ValidatingWebhookConfiguration{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == r.object().GetName()
}))).
WatchesRawSource(source.Channel(r.TriggerChannel, &handler.EnqueueRequestForObject{})).
Complete(r)
}
func (r *WritePermissions) object() *admissionregistrationv1.ValidatingWebhookConfiguration {
return &admissionregistrationv1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "kamaji-write-permissions",
},
}
}

View File

@@ -253,6 +253,19 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
//
// Register all the controllers of the soot here:
//
writePermissions := &controllers.WritePermissions{
Logger: mgr.GetLogger().WithName("writePermissions"),
Client: mgr.GetClient(),
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
WebhookNamespace: m.MigrateServiceNamespace,
WebhookServiceName: m.MigrateServiceName,
WebhookCABundle: m.MigrateCABundle,
TriggerChannel: nil,
}
if err = writePermissions.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
}
migrate := &controllers.Migrate{
WebhookNamespace: m.MigrateServiceNamespace,
WebhookServiceName: m.MigrateServiceName,
@@ -370,6 +383,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
m.sootMap[request.NamespacedName.String()] = sootItem{
triggers: []chan event.GenericEvent{
writePermissions.TriggerChannel,
migrate.TriggerChannel,
konnectivityAgent.TriggerChannel,
kubeProxy.TriggerChannel,

View File

@@ -27,7 +27,7 @@ Kamaji is an open source Kubernetes Operator that transforms any Kubernetes clus
Kamaji leverages Kubernetes Custom Resource Definitions (CRDs) to provide a fully declarative approach to managing control planes, datastores, and other resources.
- **CNCF Compliance:**
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters are [CNCF Certified Kubernetes](https://www.cncf.io/certification/software-conformance/) and compatible with standard Kubernetes tooling.
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters follow [CNCF Certified Kubernetes Software Conformance](https://www.cncf.io/certification/software-conformance/) and are compatible with standard Kubernetes tooling.
## Extensibility and Integrations
@@ -54,4 +54,4 @@ Explore the following concepts to understand how Kamaji works under the hood:
- [Tenant Worker Nodes](tenant-worker-nodes.md)
- [Konnectivity](konnectivity.md)
Kamajis architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.
Kamajis architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.

View File

@@ -166,6 +166,17 @@ helm repo update
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace --version 0.0.0+latest
```
!!! note "Alternative: Two-Step Installation"
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first:
```bash
# Step 1: Install CRDs
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
# Step 2: Install Kamaji operator
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
```
## Create Tenant Cluster
Now that our management cluster is up and running, we can create a Tenant Cluster. A Tenant Cluster is a Kubernetes cluster that is managed by Kamaji.

View File

@@ -86,6 +86,17 @@ helm install kamaji clastix/kamaji \
--set image.tag=latest
```
!!! note "Alternative: Two-Step Installation"
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first
```bash
# Step 1: Install CRDs
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
# Step 2: Install Kamaji operator
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
```
After installation, verify that Kamaji and its components are running:
```bash

View File

@@ -4,6 +4,57 @@ This guide describe a declarative way to deploy Kubernetes add-ons across multip
This way the tenant resources can be ensured from a single pane of glass, from the *Management Cluster*.
## Installing Kamaji with GitOps
For GitOps workflows using tools like FluxCD or ArgoCD, the kamaji-crds chart enables separate CRD management:
```yaml
# Step 1: HelmRelease for CRDs
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: kamaji-crds
namespace: kamaji-system
spec:
interval: 10m
chart:
spec:
chart: kamaji-crds
version: 0.0.0+latest
sourceRef:
kind: HelmRepository
name: clastix
namespace: flux-system
---
# Step 2: HelmRelease for operator
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: kamaji
namespace: kamaji-system
spec:
interval: 10m
dependsOn:
- name: kamaji-crds
chart:
spec:
chart: kamaji
version: 0.0.0+latest
sourceRef:
kind: HelmRepository
name: clastix
namespace: flux-system
```
!!! note "CRD Management Options"
The kamaji chart includes CRDs in its `crds/` directory. Using the separate kamaji-crds chart is optional and beneficial for:
- GitOps workflows requiring separate CRD lifecycle management
- Organizations with policies requiring separate CRD approval
- Scenarios where CRD updates must precede operator updates
For standard installations, using the kamaji chart alone is sufficient.
## Flux as the GitOps operator
As GitOps ensures a constant reconciliation to a Git-versioned desired state, [Flux](https://fluxcd.io) can satisfy the requirement of those scenarios. In particular, the controllers that reconcile [resources](https://fluxcd.io/flux/concepts/#reconciliation) support communicating to external clusters.

View File

@@ -0,0 +1,115 @@
# Write Permissions
Using the _Write Permissions_ section, operators can limit write operations for a given Tenant Control Plane,
where no further write actions can be made by its tenants.
This feature ensures consistency during maintenance, migrations, incident recovery, quote enforcement,
or when freezing workloads for auditing and compliance purposes.
Write Operations can limit the following actions:
- Create
- Update
- Delete
By default, all write operations are allowed.
## Enabling a Read-Only mode
You can enable ReadOnly mode by setting all the boolean fields of `TenantControlPlane.spec.writePermissions` to `true`.
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: my-control-plane
spec:
writePermissions:
blockCreate: true
blockUpdate: true
blockDelete: true
```
Once applied, the Tenant Control Plane will switch into `WriteLimited` status.
## Enforcing a quota mode
If your Tenant Control Plane has a Datastore quota, this feature allows freezing write and update operations,
but still allowing its tenants to perform a clean-up by deleting exceeding resources.
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: my-control-plane
spec:
writePermissions:
blockCreate: true
blockUpdate: true
blockDelete: false
```
!!! note "Datastore quota"
Kamaji does **not** enforce storage quota for a given Tenant Control Plane:
you have to implement it according to your business logic.
## Monitoring the status
You can verify the status of your Tenant Control Plane with `kubectl get tcp`:
```json
$: kubectl get tcp k8s-133
NAME VERSION INSTALLED VERSION STATUS CONTROL-PLANE ENDPOINT KUBECONFIG DATASTORE AGE
k8s-133 v1.33.0 v1.33.0 WriteLimited 172.18.255.100:6443 k8s-133-admin-kubeconfig default 50d
```
The `STATUS` field will display `WriteLimited` when write permissions are limited.
## How it works
When a Tenant Control Plane write status is _limited_, Kamaji creates a `ValidatingWebhookConfiguration` in the Tenant Cluster:
```
$: kubectl get validatingwebhookconfigurations
NAME WEBHOOKS AGE
kamaji-write-permissions 2 59m
```
The webhook intercepts all API requests to the Tenant Control Plane and programmatically denies any attempts to modify resources.
As a result, all changes initiated by tenants (such as `kubectl apply`, `kubectl delete`, or CRD updates) could be blocked.
!!! warning "Operators and Controller"
When the write status is limited, all actions are intercepted by the webhook.
If a Pod must be rescheduled, the webhook will deny it.
## Behaviour with limited write operations
If a tenant user tries to perform non-allowed write operations, such as:
- creating resources when `TenantControlPlane.spec.writePermissions.blockCreate` is set to `true`
- updating resources when `TenantControlPlane.spec.writePermissions.blockUpdate` is set to `true`
- deleting resources when `TenantControlPlane.spec.writePermissions.blockDelete` is set to `true`
the following error is returned:
```
Error from server (Forbidden): admission webhook "catchall.write-permissions.kamaji.clastix.io" denied the request:
the current Control Plane has limited write permissions, current changes are blocked:
removing the webhook may lead to an inconsistent state upon its completion
```
This guarantees the cluster remains in a frozen, consistent state, preventing partial updates or drift.
## Use Cases
Typical scenarios where ReadOnly mode is useful:
- **Planned Maintenance**: freeze workloads before performing upgrades or infrastructure changes.
- **Disaster Recovery**: lock the Tenant Control Plane to prevent accidental modifications during incident handling.
- **Auditing & Compliance**: ensure workloads cannot be altered during a compliance check or certification process.
- **Quota Enforcement**: preventing Datastore quote over commit in terms of storage size.
!!! info "Migrating the DataStore"
In a similar manner, when migrating a Tenant Control Plane to a different store, similar enforcement is put in place.
This is managed automatically by Kamaji: there's no need to toggle on and off the ReadOnly mode.

View File

@@ -28523,6 +28523,18 @@ DataStoreUsername by concatenating the namespace and name of the TenantControlPl
NetworkProfile specifies how the network is<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantcontrolplanespecwritepermissions">writePermissions</a></b></td>
<td>object</td>
<td>
WritePermissions allows to select which operations (create, delete, update) must be blocked:
by default, all actions are allowed, and API Server can write to its Datastore.
By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
this phase can be used to prevent Datastore quota exhaustion or for your own business logic
(e.g.: blocking creation and update, but allowing deletion to "clean up" space).<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
@@ -28597,6 +28609,14 @@ Defining the options for the Tenant Control Plane Service resource.
AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantcontrolplanespeccontrolplaneserviceadditionalportsindex">additionalPorts</a></b></td>
<td>[]object</td>
<td>
AdditionalPorts allows adding additional ports to the Service generated Kamaji
which targets the Tenant Control Plane pods.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
@@ -28633,6 +28653,75 @@ AdditionalMetadata defines which additional metadata, such as labels and annotat
</table>
<span id="tenantcontrolplanespeccontrolplaneserviceadditionalportsindex">`TenantControlPlane.spec.controlPlane.service.additionalPorts[index]`</span>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>name</b></td>
<td>string</td>
<td>
The name of this port within the Service created by Kamaji.
This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>port</b></td>
<td>integer</td>
<td>
The port that will be exposed by this service.<br/>
<br/>
<i>Format</i>: int32<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>targetPort</b></td>
<td>int or string</td>
<td>
Number or name of the port to access on the pods of the Tenant Control Plane.
Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.
If this is a string, it will be looked up as a named port in the
target Pod's container ports. If this is not specified, the value
of the 'port' field is used (an identity map).<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>appProtocol</b></td>
<td>string</td>
<td>
The application protocol for this port.
This is used as a hint for implementations to offer richer behavior for protocols that they understand.
This field follows standard Kubernetes label syntax.
Valid values are either:
* Un-prefixed protocol names - reserved for IANA standard service names (as per
RFC-6335 and https://www.iana.org/assignments/service-names).<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>protocol</b></td>
<td>enum</td>
<td>
The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".<br/>
<br/>
<i>Enum</i>: TCP, UDP, SCTP<br/>
<i>Default</i>: TCP<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="tenantcontrolplanespeccontrolplanedeployment">`TenantControlPlane.spec.controlPlane.deployment`</span>
@@ -41987,6 +42076,50 @@ Example: {"192.168.1.0/24", "10.0.0.0/8"}<br/>
</table>
<span id="tenantcontrolplanespecwritepermissions">`TenantControlPlane.spec.writePermissions`</span>
WritePermissions allows to select which operations (create, delete, update) must be blocked:
by default, all actions are allowed, and API Server can write to its Datastore.
By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
this phase can be used to prevent Datastore quota exhaustion or for your own business logic
(e.g.: blocking creation and update, but allowing deletion to "clean up" space).
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>blockCreation</b></td>
<td>boolean</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>blockDeletion</b></td>
<td>boolean</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>blockUpdate</b></td>
<td>boolean</td>
<td>
<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="tenantcontrolplanestatus">`TenantControlPlane.status`</span>
@@ -44111,7 +44244,7 @@ KubernetesVersion contains the information regarding the running Kubernetes vers
<td>
Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.<br/>
<br/>
<i>Enum</i>: Provisioning, CertificateAuthorityRotating, Upgrading, Migrating, Ready, NotReady, Sleeping<br/>
<i>Enum</i>: Unknown, Provisioning, CertificateAuthorityRotating, Upgrading, Migrating, Ready, NotReady, Sleeping, WriteLimited<br/>
<i>Default</i>: Provisioning<br/>
</td>
<td>false</td>

View File

@@ -74,6 +74,7 @@ nav:
- guides/backup-and-restore.md
- guides/certs-lifecycle.md
- guides/pausing.md
- guides/write-permissions.md
- guides/datastore-migration.md
- guides/gitops.md
- guides/console.md

21
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/JamesStewy/go-mysqldump v0.2.2
github.com/blang/semver v3.5.1+incompatible
github.com/clastix/kamaji-telemetry v1.0.0
github.com/docker/docker v28.4.0+incompatible
github.com/docker/docker v28.5.1+incompatible
github.com/go-logr/logr v1.4.3
github.com/go-pg/pg/v10 v10.15.0
github.com/go-sql-driver/mysql v1.9.3
@@ -14,8 +14,8 @@ require (
github.com/google/uuid v1.6.0
github.com/json-iterator/go v1.1.12
github.com/juju/mutex/v2 v2.0.0
github.com/nats-io/nats.go v1.46.0
github.com/onsi/ginkgo/v2 v2.25.3
github.com/nats-io/nats.go v1.47.0
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.23.2
@@ -27,16 +27,16 @@ require (
go.etcd.io/etcd/client/v3 v3.6.5
go.uber.org/automaxprocs v1.6.0
gomodules.xyz/jsonpatch/v2 v2.5.0
k8s.io/api v0.34.0
k8s.io/apimachinery v0.34.0
k8s.io/apiserver v0.34.0
k8s.io/client-go v0.34.0
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/apiserver v0.34.1
k8s.io/client-go v0.34.1
k8s.io/cluster-bootstrap v0.0.0
k8s.io/klog/v2 v2.130.1
k8s.io/kubelet v0.0.0
k8s.io/kubernetes v1.34.1
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/controller-runtime v0.22.1
sigs.k8s.io/controller-runtime v0.22.3
)
require (
@@ -163,6 +163,7 @@ require (
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
@@ -180,10 +181,10 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.34.0 // indirect
k8s.io/apiextensions-apiserver v0.34.1 // indirect
k8s.io/cli-runtime v0.0.0 // indirect
k8s.io/cloud-provider v0.0.0 // indirect
k8s.io/component-base v0.34.0 // indirect
k8s.io/component-base v0.34.1 // indirect
k8s.io/component-helpers v0.34.0 // indirect
k8s.io/controller-manager v0.34.0 // indirect
k8s.io/cri-api v0.34.0 // indirect

40
go.sum
View File

@@ -58,8 +58,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -83,6 +83,12 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -112,6 +118,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -158,6 +166,8 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
@@ -205,6 +215,10 @@ github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8S
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
@@ -233,8 +247,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8=
github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
@@ -243,8 +257,8 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -318,6 +332,14 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -407,6 +429,8 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbR
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -535,8 +559,8 @@ mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg=
sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y=
sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=

View File

@@ -117,6 +117,7 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
}
r.resource.SetLabels(utilities.MergeMaps(
r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: utilities.CertificateX509Label,

View File

@@ -104,6 +104,7 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
}
r.resource.SetLabels(utilities.MergeMaps(
r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: utilities.CertificateX509Label,

View File

@@ -153,7 +153,7 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
corev1.TLSPrivateKeyKey: ca.PrivateKey,
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
utilities.SetObjectChecksum(r.resource, r.resource.Data)

View File

@@ -106,6 +106,7 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
r.resource.Data["ca.crt"] = ca
r.resource.SetLabels(utilities.MergeMaps(
r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: utilities.CertificateX509Label,

View File

@@ -190,7 +190,7 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
utilities.SetObjectChecksum(r.resource, r.resource.Data)
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
}

View File

@@ -104,6 +104,7 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
}
r.resource.SetLabels(utilities.MergeMaps(
r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: utilities.CertificateX509Label,

View File

@@ -126,7 +126,7 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
kubeadmconstants.FrontProxyCAKeyName: ca.PrivateKey,
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
if isRotationRequested {
utilities.SetLastRotationTimestamp(r.resource)

View File

@@ -36,7 +36,9 @@ func (r *KubernetesDeploymentResource) isStatusEqual(tenantControlPlane *kamajiv
}
func (r *KubernetesDeploymentResource) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
return !r.isStatusEqual(tenantControlPlane) || tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version
return !r.isStatusEqual(tenantControlPlane) ||
tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version ||
*r.computeStatus(tenantControlPlane) != ptr.Deref(tenantControlPlane.Status.Kubernetes.Version.Status, kamajiv1alpha1.VersionUnknown)
}
func (r *KubernetesDeploymentResource) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
@@ -78,19 +80,29 @@ func (r *KubernetesDeploymentResource) GetName() string {
return "deployment"
}
func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
func (r *KubernetesDeploymentResource) computeStatus(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) *kamajiv1alpha1.KubernetesVersionStatus {
switch {
case ptr.Deref(tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, 2) == 0:
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionSleeping
case !r.isProgressingUpgrade():
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionReady
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
case r.isUpgrading(tenantControlPlane):
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionUpgrading
case r.isProvisioning(tenantControlPlane):
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionProvisioning
return &kamajiv1alpha1.VersionSleeping
case r.isNotReady():
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionNotReady
return &kamajiv1alpha1.VersionNotReady
case tenantControlPlane.Spec.WritePermissions.HasAnyLimitation():
return &kamajiv1alpha1.VersionWriteLimited
case !r.isProgressingUpgrade():
return &kamajiv1alpha1.VersionReady
case r.isUpgrading(tenantControlPlane):
return &kamajiv1alpha1.VersionUpgrading
case r.isProvisioning(tenantControlPlane):
return &kamajiv1alpha1.VersionProvisioning
default:
return &kamajiv1alpha1.VersionUnknown
}
}
func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
tenantControlPlane.Status.Kubernetes.Version.Status = r.computeStatus(tenantControlPlane)
if *tenantControlPlane.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionReady {
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
}
tenantControlPlane.Status.Kubernetes.Deployment = kamajiv1alpha1.KubernetesDeploymentStatus{

View File

@@ -151,7 +151,7 @@ func (r *KubernetesIngressResource) Define(_ context.Context, tenantControlPlane
func (r *KubernetesIngressResource) mutate(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
labels := utilities.MergeMaps(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Labels)
labels := utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Labels)
r.resource.SetLabels(labels)
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Annotations)

View File

@@ -83,7 +83,12 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
address, _ := tenantControlPlane.DeclaredControlPlaneAddress(ctx, r.Client)
return func() error {
labels := utilities.MergeMaps(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Labels)
labels := utilities.MergeMaps(
r.resource.GetLabels(),
utilities.KamajiLabels(
tenantControlPlane.GetName(), r.GetName()),
tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Labels,
)
r.resource.SetLabels(labels)
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Annotations)
@@ -93,14 +98,37 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
}
if len(r.resource.Spec.Ports) == 0 {
if r.resource.Spec.Ports == nil {
r.resource.Spec.Ports = make([]corev1.ServicePort, 1)
}
r.resource.Spec.Ports[0].Name = "kube-apiserver"
r.resource.Spec.Ports[0].Protocol = corev1.ProtocolTCP
r.resource.Spec.Ports[0].Port = tenantControlPlane.Spec.NetworkProfile.Port
r.resource.Spec.Ports[0].TargetPort = intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port)
var ports []corev1.ServicePort
for i, port := range r.resource.Spec.Ports {
switch {
case i == 0:
port.Name = "kube-apiserver"
port.Protocol = corev1.ProtocolTCP
port.Port = tenantControlPlane.Spec.NetworkProfile.Port
port.TargetPort = intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port)
ports = append(ports, port)
case i == 1 && port.Name == "konnectivity-server":
ports = append(ports, port)
}
}
for _, port := range tenantControlPlane.Spec.ControlPlane.Service.AdditionalPorts {
ports = append(ports, corev1.ServicePort{
Name: port.Name,
Protocol: port.Protocol,
AppProtocol: port.AppProtocol,
Port: port.Port,
TargetPort: port.TargetPort,
NodePort: 0,
})
}
r.resource.Spec.Ports = ports
switch tenantControlPlane.Spec.ControlPlane.Service.ServiceType {
case kamajiv1alpha1.ServiceTypeLoadBalancer:

View File

@@ -5,6 +5,7 @@ package konnectivity
import (
"context"
"crypto/x509"
"fmt"
"time"
@@ -102,6 +103,16 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
return func() error {
logger := log.FromContext(ctx, "resource", r.GetName())
// Retrieving the TenantControlPlane CA:
// this is required to trigger a new generation in case of Certificate Authority rotation.
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
secretCA := &corev1.Secret{}
if err := r.Client.Get(ctx, namespacedName, secretCA); err != nil {
logger.Error(err, "cannot retrieve the CA secret")
return err
}
r.resource.SetLabels(utilities.MergeMaps(
r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
@@ -119,23 +130,20 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
isRotationRequested := utilities.IsRotationRequested(r.resource)
if checksum := tenantControlPlane.Status.Addons.Konnectivity.Certificate.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.CalculateMapChecksum(r.resource.Data)) {
isCAValid, err := crypto.VerifyCertificate(r.resource.Data[corev1.TLSCertKey], secretCA.Data[kubeadmconstants.CACertName], x509.ExtKeyUsageServerAuth)
if err != nil {
logger.Info(fmt.Sprintf("certificate-authority verify failed: %s", err.Error()))
}
isValid, err := crypto.IsValidCertificateKeyPairBytes(r.resource.Data[corev1.TLSCertKey], r.resource.Data[corev1.TLSPrivateKeyKey], r.CertExpirationThreshold)
if err != nil {
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", konnectivityCertAndKeyBaseName, err.Error()))
}
if isValid {
if isCAValid && isValid {
return nil
}
}
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
secretCA := &corev1.Secret{}
if err := r.Client.Get(ctx, namespacedName, secretCA); err != nil {
logger.Error(err, "cannot retrieve the CA secret")
return err
}
ca := kubeadm.CertificatePrivateKeyPair{
Name: kubeadmconstants.CACertAndKeyBaseName,
Certificate: secretCA.Data[kubeadmconstants.CACertName],

View File

@@ -96,7 +96,7 @@ func (r *KubeadmConfigResource) mutate(ctx context.Context, tenantControlPlane *
return err
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
params := kubeadm.Parameters{
TenantControlPlaneAddress: address,

View File

@@ -172,6 +172,7 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
}
r.resource.SetLabels(utilities.MergeMaps(
r.resource.GetLabels(),
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: utilities.CertificateKubeconfigLabel,

View File

@@ -122,7 +122,7 @@ func (r *SACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
kubeadmconstants.ServiceAccountPrivateKeyName: sa.PrivateKey,
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
if isRotationRequested {
utilities.SetLastRotationTimestamp(r.resource)

View File

@@ -25,18 +25,21 @@ type handlersChainer struct {
//nolint:gocognit
func (h handlersChainer) Handler(object runtime.Object, routeHandlers ...handlers.Handler) admission.HandlerFunc {
return func(ctx context.Context, req admission.Request) admission.Response {
decodedObj, oldDecodedObj := object.DeepCopyObject(), object.DeepCopyObject()
var decodedObj, oldDecodedObj runtime.Object
if object != nil {
decodedObj, oldDecodedObj = object.DeepCopyObject(), object.DeepCopyObject()
switch req.Operation {
case admissionv1.Delete:
// When deleting the OldObject struct field contains the object being deleted:
// https://github.com/kubernetes/kubernetes/pull/76346
if err := h.decoder.DecodeRaw(req.OldObject, decodedObj); err != nil {
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode deleted object into %T", object)))
}
default:
if err := h.decoder.Decode(req, decodedObj); err != nil {
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode into %T", object)))
switch req.Operation {
case admissionv1.Delete:
// When deleting the OldObject struct field contains the object being deleted:
// https://github.com/kubernetes/kubernetes/pull/76346
if err := h.decoder.DecodeRaw(req.OldObject, decodedObj); err != nil {
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode deleted object into %T", object)))
}
default:
if err := h.decoder.Decode(req, decodedObj); err != nil {
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode into %T", object)))
}
}
}

View File

@@ -0,0 +1,32 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"context"
"fmt"
"gomodules.xyz/jsonpatch/v2"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
type WritePermission struct{}
func (f WritePermission) OnCreate(runtime.Object) AdmissionResponse {
return f.response
}
func (f WritePermission) OnDelete(runtime.Object) AdmissionResponse {
return f.response
}
func (f WritePermission) OnUpdate(runtime.Object, runtime.Object) AdmissionResponse {
return f.response
}
func (f WritePermission) response(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
return nil, fmt.Errorf("the current Control Plane has limited write permissions, current changes are blocked: " +
"removing the webhook may lead to an inconsistent state upon its completion")
}

View File

@@ -4,7 +4,6 @@
package routes
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)
@@ -15,5 +14,5 @@ func (t TenantControlPlaneMigrate) GetPath() string {
}
func (t TenantControlPlaneMigrate) GetObject() runtime.Object {
return &corev1.Namespace{}
return nil
}

View File

@@ -0,0 +1,18 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package routes
import (
"k8s.io/apimachinery/pkg/runtime"
)
type TenantControlPlaneWritePermission struct{}
func (t TenantControlPlaneWritePermission) GetPath() string {
return "/write-permission"
}
func (t TenantControlPlaneWritePermission) GetObject() runtime.Object {
return nil
}