mirror of
https://github.com/clastix/kamaji.git
synced 2026-02-27 16:23:54 +00:00
Compare commits
8 Commits
edge-25.7.
...
edge-25.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6f20674ec | ||
|
|
0990317595 | ||
|
|
382d3274f3 | ||
|
|
55516c833e | ||
|
|
cac1631523 | ||
|
|
d1eb860918 | ||
|
|
6c76bd6a97 | ||
|
|
462d52332c |
10
.github/release-template.md
vendored
Normal file
10
.github/release-template.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
This edge release can be pulled from Docker Hub as follows:
|
||||
|
||||
```
|
||||
docker pull clastix/kamaji:$TAG
|
||||
```
|
||||
|
||||
> As from the v1.0.0 release, CLASTIX no longer provides stable release artefacts.
|
||||
>
|
||||
> Stable release artefacts are offered on a subscription basis by CLASTIX, the main Kamaji project contributor.
|
||||
> Learn more from CLASTIX's [Support](https://clastix.io/support/) section.
|
||||
46
.github/workflows/release.yml
vendored
Normal file
46
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Weekly Edge Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 7 * * 1' # Every Monday at 9 AM CET
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: generating date metadata
|
||||
id: date
|
||||
run: |
|
||||
CURRENT_DATE=$(date -u +'%Y-%m-%d')
|
||||
YY=$(date -u +'%y')
|
||||
M=$(date -u +'%_m' | sed 's/ //g')
|
||||
FIRST_OF_MONTH=$(date -u -d "$CURRENT_DATE" +%Y-%m-01)
|
||||
WEEK_NUM=$(( (($(date -u +%s) - $(date -u -d "$FIRST_OF_MONTH" +%s)) / 86400 + $(date -u -d "$FIRST_OF_MONTH" +%u) - 1) / 7 + 1 ))
|
||||
|
||||
echo "yy=$YY" >> $GITHUB_OUTPUT
|
||||
echo "month=$M" >> $GITHUB_OUTPUT
|
||||
echo "week=$WEEK_NUM" >> $GITHUB_OUTPUT
|
||||
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
|
||||
- name: generating tag metadata
|
||||
id: tag
|
||||
run: |
|
||||
TAG="edge-${{ steps.date.outputs.yy }}.${{ steps.date.outputs.month }}.${{ steps.date.outputs.week }}"
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
- name: generate release notes from template
|
||||
run: |
|
||||
export TAG="${{ steps.tag.outputs.tag }}"
|
||||
envsubst < .github/release-template.md > release-notes.md
|
||||
- name: create GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create "${{ steps.tag.outputs.tag }}" \
|
||||
--title "${{ steps.tag.outputs.tag }}" \
|
||||
--notes-file release-notes.md
|
||||
@@ -122,6 +122,12 @@ type ExternalKubernetesObjectStatus struct {
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
}
|
||||
|
||||
type KonnectivityAgentStatus struct {
|
||||
ExternalKubernetesObjectStatus `json:",inline"`
|
||||
|
||||
Mode KonnectivityAgentMode `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
// KonnectivityStatus defines the status of Konnectivity as Addon.
|
||||
type KonnectivityStatus struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
@@ -130,7 +136,7 @@ type KonnectivityStatus struct {
|
||||
Kubeconfig KubeconfigStatus `json:"kubeconfig,omitempty"`
|
||||
ServiceAccount ExternalKubernetesObjectStatus `json:"sa,omitempty"`
|
||||
ClusterRoleBinding ExternalKubernetesObjectStatus `json:"clusterrolebinding,omitempty"`
|
||||
Agent ExternalKubernetesObjectStatus `json:"agent,omitempty"`
|
||||
Agent KonnectivityAgentStatus `json:"agent,omitempty"`
|
||||
Service KubernetesServiceStatus `json:"service,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,15 @@ type KonnectivityServerSpec struct {
|
||||
ExtraArgs ExtraArgs `json:"extraArgs,omitempty"`
|
||||
}
|
||||
|
||||
type KonnectivityAgentMode string
|
||||
|
||||
var (
|
||||
KonnectivityAgentModeDaemonSet KonnectivityAgentMode = "DaemonSet"
|
||||
KonnectivityAgentModeDeployment KonnectivityAgentMode = "Deployment"
|
||||
)
|
||||
|
||||
//+kubebuilder:validation:XValidation:rule="!(self.mode == 'DaemonSet' && has(self.replicas) && self.replicas != 0) && !(self.mode == 'Deployment' && self.replicas == 0)",message="replicas must be 0 when mode is DaemonSet, and greater than 0 when mode is Deployment"
|
||||
|
||||
type KonnectivityAgentSpec struct {
|
||||
// AgentImage defines the container image for Konnectivity's agent.
|
||||
//+kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
@@ -248,13 +257,21 @@ type KonnectivityAgentSpec struct {
|
||||
//+kubebuilder:default={{key: "CriticalAddonsOnly", operator: "Exists"}}
|
||||
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
|
||||
ExtraArgs ExtraArgs `json:"extraArgs,omitempty"`
|
||||
// Mode allows specifying the Agent deployment mode: Deployment, or DaemonSet (default).
|
||||
//+kubebuilder:default="DaemonSet"
|
||||
//+kubebuilder:validation:Enum=DaemonSet;Deployment
|
||||
Mode KonnectivityAgentMode `json:"mode,omitempty"`
|
||||
// Replicas defines the number of replicas when Mode is Deployment.
|
||||
// Must be 0 if Mode is DaemonSet.
|
||||
//+kubebuilder:validation:Optional
|
||||
Replicas int32 `json:"replicas,omitempty"`
|
||||
}
|
||||
|
||||
// KonnectivitySpec defines the spec for Konnectivity.
|
||||
type KonnectivitySpec struct {
|
||||
//+kubebuilder:default={version:"v0.28.6",image:"registry.k8s.io/kas-network-proxy/proxy-server",port:8132}
|
||||
KonnectivityServerSpec KonnectivityServerSpec `json:"server,omitempty"`
|
||||
//+kubebuilder:default={version:"v0.28.6",image:"registry.k8s.io/kas-network-proxy/proxy-agent"}
|
||||
//+kubebuilder:default={version:"v0.28.6",image:"registry.k8s.io/kas-network-proxy/proxy-agent",mode:"DaemonSet"}
|
||||
KonnectivityAgentSpec KonnectivityAgentSpec `json:"agent,omitempty"`
|
||||
}
|
||||
|
||||
@@ -273,6 +290,7 @@ type AddonsSpec struct {
|
||||
// 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"
|
||||
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreUsername) || has(self.dataStoreUsername)", message="unsetting the dataStoreUsername is not supported"
|
||||
// +kubebuilder:validation:XValidation:rule="!has(self.networkProfile.loadBalancerSourceRanges) || (size(self.networkProfile.loadBalancerSourceRanges) == 0 || self.controlPlane.service.serviceType == 'LoadBalancer')", message="LoadBalancer source ranges are supported only with LoadBalancer service type"
|
||||
// +kubebuilder:validation:XValidation:rule="!has(self.networkProfile.loadBalancerClass) || self.controlPlane.service.serviceType == 'LoadBalancer'", message="LoadBalancerClass is supported only with LoadBalancer service type"
|
||||
// +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"
|
||||
@@ -290,8 +308,14 @@ type TenantControlPlaneSpec struct {
|
||||
// to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
|
||||
// DataStoreSchema by concatenating the namespace and name of the TenantControlPlane.
|
||||
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the dataStoreSchema is not supported"
|
||||
DataStoreSchema string `json:"dataStoreSchema,omitempty"`
|
||||
ControlPlane ControlPlane `json:"controlPlane"`
|
||||
DataStoreSchema string `json:"dataStoreSchema,omitempty"`
|
||||
// DataStoreUsername allows to specify the username of the database (for relational DataStores). This
|
||||
// value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreUsername values are unique. It's up
|
||||
// to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
|
||||
// DataStoreUsername by concatenating the namespace and name of the TenantControlPlane.
|
||||
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the dataStoreUsername is not supported"
|
||||
DataStoreUsername string `json:"dataStoreUsername,omitempty"`
|
||||
ControlPlane ControlPlane `json:"controlPlane"`
|
||||
// Kubernetes specification for tenant control plane
|
||||
Kubernetes KubernetesSpec `json:"kubernetes"`
|
||||
// NetworkProfile specifies how the network is
|
||||
|
||||
@@ -808,6 +808,22 @@ func (in *KonnectivityAgentSpec) DeepCopy() *KonnectivityAgentSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KonnectivityAgentStatus) DeepCopyInto(out *KonnectivityAgentStatus) {
|
||||
*out = *in
|
||||
in.ExternalKubernetesObjectStatus.DeepCopyInto(&out.ExternalKubernetesObjectStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KonnectivityAgentStatus.
|
||||
func (in *KonnectivityAgentStatus) DeepCopy() *KonnectivityAgentStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KonnectivityAgentStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KonnectivityConfigMap) DeepCopyInto(out *KonnectivityConfigMap) {
|
||||
*out = *in
|
||||
|
||||
@@ -96,6 +96,7 @@ spec:
|
||||
agent:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
mode: DaemonSet
|
||||
version: v0.28.6
|
||||
properties:
|
||||
extraArgs:
|
||||
@@ -111,6 +112,19 @@ spec:
|
||||
default: registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
description: AgentImage defines the container image for Konnectivity's agent.
|
||||
type: string
|
||||
mode:
|
||||
default: DaemonSet
|
||||
description: 'Mode allows specifying the Agent deployment mode: Deployment, or DaemonSet (default).'
|
||||
enum:
|
||||
- DaemonSet
|
||||
- Deployment
|
||||
type: string
|
||||
replicas:
|
||||
description: |-
|
||||
Replicas defines the number of replicas when Mode is Deployment.
|
||||
Must be 0 if Mode is DaemonSet.
|
||||
format: int32
|
||||
type: integer
|
||||
tolerations:
|
||||
default:
|
||||
- key: CriticalAddonsOnly
|
||||
@@ -160,6 +174,9 @@ spec:
|
||||
description: Version for Konnectivity agent.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: replicas must be 0 when mode is DaemonSet, and greater than 0 when mode is Deployment
|
||||
rule: '!(self.mode == ''DaemonSet'' && has(self.replicas) && self.replicas != 0) && !(self.mode == ''Deployment'' && self.replicas == 0)'
|
||||
server:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-server
|
||||
@@ -6469,6 +6486,16 @@ spec:
|
||||
x-kubernetes-validations:
|
||||
- message: changing the dataStoreSchema is not supported
|
||||
rule: self == oldSelf
|
||||
dataStoreUsername:
|
||||
description: |-
|
||||
DataStoreUsername allows to specify the username of the database (for relational DataStores). This
|
||||
value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreUsername values are unique. It's up
|
||||
to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
|
||||
DataStoreUsername by concatenating the namespace and name of the TenantControlPlane.
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: changing the dataStoreUsername is not supported
|
||||
rule: self == oldSelf
|
||||
kubernetes:
|
||||
description: Kubernetes specification for tenant control plane
|
||||
properties:
|
||||
@@ -6653,6 +6680,8 @@ spec:
|
||||
rule: '!has(oldSelf.dataStore) || has(self.dataStore)'
|
||||
- message: unsetting the dataStoreSchema is not supported
|
||||
rule: '!has(oldSelf.dataStoreSchema) || has(self.dataStoreSchema)'
|
||||
- message: unsetting the dataStoreUsername is not supported
|
||||
rule: '!has(oldSelf.dataStoreUsername) || has(self.dataStoreUsername)'
|
||||
- message: LoadBalancer source ranges are supported only with LoadBalancer service type
|
||||
rule: '!has(self.networkProfile.loadBalancerSourceRanges) || (size(self.networkProfile.loadBalancerSourceRanges) == 0 || self.controlPlane.service.serviceType == ''LoadBalancer'')'
|
||||
- message: LoadBalancerClass is supported only with LoadBalancer service type
|
||||
@@ -6685,6 +6714,8 @@ spec:
|
||||
description: Last time when k8s object was updated
|
||||
format: date-time
|
||||
type: string
|
||||
mode:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
|
||||
@@ -22,3 +22,5 @@ spec:
|
||||
konnectivity:
|
||||
server:
|
||||
port: 8132
|
||||
agent:
|
||||
mode: DaemonSet
|
||||
|
||||
@@ -72,12 +72,12 @@ func (s *CertificateLifecycle) Reconcile(ctx context.Context, request reconcile.
|
||||
var err error
|
||||
|
||||
switch checkType {
|
||||
case "x509":
|
||||
case utilities.CertificateX509Label:
|
||||
crt, err = s.extractCertificateFromBareSecret(secret)
|
||||
case "kubeconfig":
|
||||
case utilities.CertificateKubeconfigLabel:
|
||||
crt, err = s.extractCertificateFromKubeconfig(secret)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported strategy, %s", checkType)
|
||||
return reconcile.Result{}, fmt.Errorf("unsupported strategy, %q", checkType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -152,7 +152,7 @@ func (s *CertificateLifecycle) extractCertificateFromKubeconfig(secret corev1.Se
|
||||
func (s *CertificateLifecycle) SetupWithManager(mgr controllerruntime.Manager) error {
|
||||
s.client = mgr.GetClient()
|
||||
|
||||
supportedStrategies := sets.New[string]("x509", "kubeconfig")
|
||||
supportedStrategies := sets.New[string](utilities.CertificateX509Label, utilities.CertificateKubeconfigLabel)
|
||||
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Secret{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
# Konnectivity
|
||||
|
||||
In traditional Kubernetes deployments, the control plane components need to communicate directly with worker nodes for various operations like executing commands in pods, retrieving logs, or managing port forwards. However, in many real-world environments, especially those spanning multiple networks or cloud providers, direct communication isn't always possible or desirable. This is where Konnectivity comes in.
|
||||
In traditional Kubernetes deployments, the control plane components need to communicate directly with worker nodes for various operations
|
||||
like executing commands in pods, retrieving logs, or managing port forwards.
|
||||
However, in many real-world environments, especially those spanning multiple networks or cloud providers,
|
||||
direct communication isn't always possible or desirable. This is where Konnectivity comes in.
|
||||
|
||||
## Understanding Konnectivity in Kamaji
|
||||
|
||||
Kamaji integrates [Konnectivity](https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/) as a core component of its architecture. Each Tenant Control Plane pod includes a konnectivity-server running as a sidecar container, which establishes and maintains secure tunnels with agents running on the worker nodes. This design ensures reliable communication even in complex network environments.
|
||||
Kamaji integrates [Konnectivity](https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/) as a core component of its architecture.
|
||||
Each Tenant Control Plane pod includes a konnectivity-server running as a sidecar container,
|
||||
which establishes and maintains secure tunnels with agents running on the worker nodes.
|
||||
|
||||
This design ensures reliable communication even in complex network environments.
|
||||
|
||||
The Konnectivity service consists of two main components:
|
||||
|
||||
1. **Konnectivity Server:**
|
||||
Runs alongside the control plane components in each Tenant Control Plane pod and is exposed on port 8132. It manages connections from worker nodes and routes traffic appropriately.
|
||||
Runs alongside the control plane components in each Tenant Control Plane pod and is exposed on port 8132.
|
||||
It manages connections from worker nodes and routes traffic appropriately.
|
||||
|
||||
2. **Konnectivity Agent:**
|
||||
Runs on each worker node and initiates outbound connections to its control plane's Konnectivity server. These connections are maintained to create a reliable tunnel for all control plane to worker node communication.
|
||||
Runs on worker nodes as _DaemonSet_ or _Deployment_ and initiates outbound connections to its control plane's Konnectivity server.
|
||||
These connections are maintained to create a reliable tunnel for all control plane to worker node communications.
|
||||
|
||||
## How It Works
|
||||
|
||||
When a worker node joins a Tenant Cluster, the Konnectivity agents automatically establish connections to their designated Konnectivity server. These connections are maintained continuously, ensuring reliable communication paths between the control plane and worker nodes.
|
||||
When a worker node joins a Tenant Cluster, the Konnectivity agents automatically establish connections to their designated Konnectivity server.
|
||||
These connections are maintained continuously, ensuring reliable communication paths between the control plane and worker nodes.
|
||||
|
||||
All traffic from the control plane to worker nodes flows through these established tunnels, enabling operations such as:
|
||||
|
||||
@@ -28,10 +38,51 @@ All traffic from the control plane to worker nodes flows through these establish
|
||||
|
||||
## Configuration and Management
|
||||
|
||||
Konnectivity is enabled by default in Kamaji, as it's considered a best practice for modern Kubernetes deployments. However, it can be disabled if your environment has different requirements or if you need to use alternative networking solutions.
|
||||
Konnectivity is enabled by default in Kamaji, as it's considered a best practice for modern Kubernetes deployments.
|
||||
However, it can be disabled if your environment has different requirements, or if you need to use alternative networking solutions.
|
||||
|
||||
The service is automatically configured when worker nodes join a cluster, without requiring any operational overhead. The connection details are managed as part of the standard node bootstrap process, making it transparent to cluster operators and users.
|
||||
The service is automatically configured when worker nodes join a cluster, without requiring any operational overhead.
|
||||
The connection details are managed as part of the standard node bootstrap process,
|
||||
making it transparent to cluster operators and users.
|
||||
|
||||
## Agent delivery mode
|
||||
|
||||
You can customise the Konnectivity Agent delivery mode via the Tenant Control Plane definition
|
||||
using the field `tenantcontrolplane.spec.addons.konnectivity.agent.mode`.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: konnectivity-example
|
||||
spec:
|
||||
controlPlane:
|
||||
deployment:
|
||||
replicas: 2
|
||||
service:
|
||||
serviceType: LoadBalancer
|
||||
kubernetes:
|
||||
version: "v1.33.0"
|
||||
networkProfile:
|
||||
port: 6443
|
||||
addons:
|
||||
konnectivity:
|
||||
server:
|
||||
port: 8132
|
||||
agent:
|
||||
## DaemonSet, Deployment
|
||||
mode: DaemonSet
|
||||
## When mode is Deployment, specify the desired Agent replicas
|
||||
# replicas: 2
|
||||
```
|
||||
|
||||
Available strategies are the following:
|
||||
- `DaemonSet`: runs on every node
|
||||
- `Deployment`: useful to decrease the resource footprint in certain workloads cluster,
|
||||
it allows customising also the amount of deployed replicas via the field
|
||||
`tenantcontrolplane.spec.addons.konnectivity.agent.replicas`.
|
||||
|
||||
---
|
||||
|
||||
By integrating Konnectivity as a core feature, Kamaji ensures that your Tenant Clusters can operate reliably and securely across any network topology, making it easier to build and manage distributed Kubernetes environments at scale.
|
||||
By integrating Konnectivity as a core feature, Kamaji ensures that your Tenant Clusters can operate reliably and securely across any network topology,
|
||||
making it easier to build and manage distributed Kubernetes environments at scale.
|
||||
|
||||
@@ -160,9 +160,6 @@ spec:
|
||||
konnectivity:
|
||||
server:
|
||||
port: ${TENANT_PROXY_PORT}
|
||||
resources: {}
|
||||
client:
|
||||
resources: {}
|
||||
EOF
|
||||
|
||||
kubectl -n ${TENANT_NAMESPACE} apply -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml
|
||||
|
||||
@@ -19,50 +19,44 @@ All the certificates are created with the `kubeadm` defaults, thus their validit
|
||||
|
||||
## How to rotate certificates
|
||||
|
||||
If you need to manually rotate one of these certificates, the required operation is the deletion for the given Secret.
|
||||
All certificates can be rotated at the same time, or one by one: this is possible by annotating resources using
|
||||
the well-known annotation `certs.kamaji.clastix.io/rotate`.
|
||||
|
||||
```
|
||||
$: kubectl get secret
|
||||
NAME TYPE DATA AGE
|
||||
k8s-126-admin-kubeconfig Opaque 1 12m
|
||||
k8s-126-api-server-certificate Opaque 2 12m
|
||||
k8s-126-api-server-kubelet-client-certificate Opaque 2 3h45m
|
||||
k8s-126-ca Opaque 4 3h45m
|
||||
k8s-126-controller-manager-kubeconfig Opaque 1 3h45m
|
||||
k8s-126-datastore-certificate Opaque 3 3h45m
|
||||
k8s-126-datastore-config Opaque 4 3h45m
|
||||
k8s-126-front-proxy-ca-certificate Opaque 2 3h45m
|
||||
k8s-126-front-proxy-client-certificate Opaque 2 3h45m
|
||||
k8s-126-konnectivity-certificate kubernetes.io/tls 2 3h45m
|
||||
k8s-126-konnectivity-kubeconfig Opaque 1 3h45m
|
||||
k8s-126-sa-certificate Opaque 2 3h45m
|
||||
k8s-126-scheduler-kubeconfig Opaque 1 3h45m
|
||||
k8s-133-admin-kubeconfig Opaque 1 12m
|
||||
k8s-133-api-server-certificate Opaque 2 12m
|
||||
k8s-133-api-server-kubelet-client-certificate Opaque 2 3h45m
|
||||
k8s-133-ca Opaque 4 3h45m
|
||||
k8s-133-controller-manager-kubeconfig Opaque 1 3h45m
|
||||
k8s-133-datastore-certificate Opaque 3 3h45m
|
||||
k8s-133-datastore-config Opaque 4 3h45m
|
||||
k8s-133-front-proxy-ca-certificate Opaque 2 3h45m
|
||||
k8s-133-front-proxy-client-certificate Opaque 2 3h45m
|
||||
k8s-133-konnectivity-certificate kubernetes.io/tls 2 3h45m
|
||||
k8s-133-konnectivity-kubeconfig Opaque 1 3h45m
|
||||
k8s-133-sa-certificate Opaque 2 3h45m
|
||||
k8s-133-scheduler-kubeconfig Opaque 1 3h45m
|
||||
```
|
||||
|
||||
Once this operation is performed, Kamaji will be notified of the missing certificate, and it will create it back.
|
||||
Once this operation is performed, Kamaji will trigger a certificate renewal,
|
||||
reporting the rotation date time as the annotation `certs.kamaji.clastix.io/rotate` value.
|
||||
|
||||
```
|
||||
$: kubectl delete secret -l kamaji.clastix.io/certificate_lifecycle_controller=x509
|
||||
secret "k8s-126-api-server-certificate" deleted
|
||||
secret "k8s-126-api-server-kubelet-client-certificate" deleted
|
||||
secret "k8s-126-front-proxy-client-certificate" deleted
|
||||
secret "k8s-126-konnectivity-certificate" deleted
|
||||
$: kubectl annotate secret -l kamaji.clastix.io/certificate_lifecycle_controller=x509 certs.kamaji.clastix.io/rotate=""
|
||||
secret/k8s-133-api-server-certificate annotated
|
||||
secret/k8s-133-api-server-kubelet-client-certificate annotated
|
||||
secret/k8s-133-datastore-certificate annotated
|
||||
secret/k8s-133-front-proxy-client-certificate annotated
|
||||
secret/k8s-133-konnectivity-certificate annotated
|
||||
|
||||
$: kubectl delete secret -l kamaji.clastix.io/certificate_lifecycle_controller=x509
|
||||
NAME TYPE DATA AGE
|
||||
k8s-126-admin-kubeconfig Opaque 1 15m
|
||||
k8s-126-api-server-certificate Opaque 2 12s
|
||||
k8s-126-api-server-kubelet-client-certificate Opaque 2 12s
|
||||
k8s-126-ca Opaque 4 3h48m
|
||||
k8s-126-controller-manager-kubeconfig Opaque 1 3h48m
|
||||
k8s-126-datastore-certificate Opaque 3 3h48m
|
||||
k8s-126-datastore-config Opaque 4 3h48m
|
||||
k8s-126-front-proxy-ca-certificate Opaque 2 3h48m
|
||||
k8s-126-front-proxy-client-certificate Opaque 2 12s
|
||||
k8s-126-konnectivity-certificate kubernetes.io/tls 2 11s
|
||||
k8s-126-konnectivity-kubeconfig Opaque 1 3h48m
|
||||
k8s-126-sa-certificate Opaque 2 3h48m
|
||||
k8s-126-scheduler-kubeconfig Opaque 1 3h48m
|
||||
$: kubectl get secrets -l kamaji.clastix.io/certificate_lifecycle_controller=x509 -ojson | jq -r '.items[] | "\(.metadata.name) rotated at \(.metadata.annotations["certs.kamaji.clastix.io/rotate"])"'
|
||||
k8s-133-api-server-certificate rotated at 2025-07-15 15:15:08.842191367 +0200 CEST m=+325.785000014
|
||||
k8s-133-api-server-kubelet-client-certificate rotated at 2025-07-15 15:15:10.468139865 +0200 CEST m=+327.410948506
|
||||
k8s-133-datastore-certificate rotated at 2025-07-15 15:15:15.454468752 +0200 CEST m=+332.397277417
|
||||
k8s-133-front-proxy-client-certificate rotated at 2025-07-15 15:15:13.279920467 +0200 CEST m=+330.222729097
|
||||
k8s-133-konnectivity-certificate rotated at 2025-07-15 15:15:17.361431671 +0200 CEST m=+334.304240277
|
||||
```
|
||||
|
||||
You can notice the secrets have been automatically created back, as well as a TenantControlPlane rollout with the updated certificates.
|
||||
@@ -70,23 +64,24 @@ You can notice the secrets have been automatically created back, as well as a Te
|
||||
```
|
||||
$: kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
k8s-126-76768bdf89-82w8g 4/4 Running 0 58s
|
||||
k8s-126-76768bdf89-fwltl 4/4 Running 0 58s
|
||||
k8s-133-67bf496c8c-27bmp 4/4 Running 0 4m52s
|
||||
k8s-133-67bf496c8c-x4t76 4/4 Running 0 4m52s
|
||||
```
|
||||
|
||||
The same occurs with the `kubeconfig` ones.
|
||||
|
||||
```
|
||||
$: kubectl delete secret -l kamaji.clastix.io/certificate_lifecycle_controller=kubeconfig
|
||||
secret "k8s-126-admin-kubeconfig" deleted
|
||||
secret "k8s-126-controller-manager-kubeconfig" deleted
|
||||
secret "k8s-126-konnectivity-kubeconfig" deleted
|
||||
secret "k8s-126-scheduler-kubeconfig" deleted
|
||||
$: kubectl annotate secret -l kamaji.clastix.io/certificate_lifecycle_controller=kubeconfig certs.kamaji.clastix.io/rotate=""
|
||||
secret/k8s-133-admin-kubeconfig annotated
|
||||
secret/k8s-133-controller-manager-kubeconfig annotated
|
||||
secret/k8s-133-konnectivity-kubeconfig annotated
|
||||
secret/k8s-133-scheduler-kubeconfig annotated
|
||||
|
||||
$: kubectl get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
k8s-126-576c775b5d-2gr9h 4/4 Running 0 50s
|
||||
k8s-126-576c775b5d-jmvlm 4/4 Running 0 50s
|
||||
$: kubectl get secrets -l kamaji.clastix.io/certificate_lifecycle_controller=kubeconfig -ojson | jq -r '.items[] | "\(.metadata.name) rotated at \(.metadata.annotations["certs.kamaji.clastix.io/rotate"])"'
|
||||
k8s-133-admin-kubeconfig rotated at 2025-07-15 15:20:41.688181782 +0200 CEST m=+658.630990441
|
||||
k8s-133-controller-manager-kubeconfig rotated at 2025-07-15 15:20:42.712211056 +0200 CEST m=+659.655019677
|
||||
k8s-133-konnectivity-kubeconfig rotated at 2025-07-15 15:20:46.405567865 +0200 CEST m=+663.348376504
|
||||
k8s-133-scheduler-kubeconfig rotated at 2025-07-15 15:20:46.333718563 +0200 CEST m=+663.276527216
|
||||
```
|
||||
|
||||
## Automatic certificates rotation
|
||||
@@ -108,11 +103,11 @@ e.g.: set the value `7d` to trigger the renewal a week before the effective expi
|
||||
|
||||
Kamaji is also taking care of your Tenant Clusters Certificate Authority.
|
||||
|
||||
This can be rotated manually by deleting the following secret.
|
||||
This can be rotated manually like other certificates by using the annotation `certs.kamaji.clastix.io/rotate`
|
||||
|
||||
```
|
||||
$: kubectl delete secret k8s-126-ca
|
||||
secret "k8s-126-ca" deleted
|
||||
$: kubectl annotate secret k8s-133-ca certs.kamaji.clastix.io/rotate=""
|
||||
secret/k8s-133-ca annotated
|
||||
```
|
||||
|
||||
Once this occurs the TenantControlPlane will enter in the `CertificateAuthorityRotating` status.
|
||||
@@ -120,26 +115,26 @@ Once this occurs the TenantControlPlane will enter in the `CertificateAuthorityR
|
||||
```
|
||||
$: kubectl get tcp -w
|
||||
NAME VERSION STATUS CONTROL-PLANE ENDPOINT KUBECONFIG DATASTORE AGE
|
||||
k8s-126 v1.26.0 Ready 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 Ready 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 Ready 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 Ready 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 Ready 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-126 v1.26.0 Ready 172.18.255.200:6443 k8s-126-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 Ready 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 CertificateAuthorityRotating 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 Ready 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 Ready 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 Ready 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 Ready 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
k8s-133 v1.33.0 Ready 172.18.255.200:6443 k8s-133-admin-kubeconfig default 3h58m
|
||||
```
|
||||
|
||||
This operation is intended to be performed manually since a new Certificate Authority requires the restart of all the components, as well as of the nodes:
|
||||
in such case, you will need to distribute the new Certificate Authority and the new nodes certificates.
|
||||
This operation is intended to be performed manually since a new Certificate Authority requires the restart of all the components,
|
||||
as well as of the nodes: in such a case, you will need to distribute the new Certificate Authority and the new nodes certificates.
|
||||
|
||||
Given the sensibility of such operation, the `Secret` controller will not check the _CA_, which is offering validity of 10 years as `kubeadm` default values.
|
||||
|
||||
@@ -27049,6 +27049,16 @@ to the user to avoid clashes between different TenantControlPlanes. If not set u
|
||||
DataStoreSchema by concatenating the namespace and name of the TenantControlPlane.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>dataStoreUsername</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
DataStoreUsername allows to specify the username of the database (for relational DataStores). This
|
||||
value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreUsername values are unique. It's up
|
||||
to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
|
||||
DataStoreUsername by concatenating the namespace and name of the TenantControlPlane.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b><a href="#tenantcontrolplanespecnetworkprofile">networkProfile</a></b></td>
|
||||
<td>object</td>
|
||||
@@ -39576,7 +39586,7 @@ Enables the Konnectivity addon in the Tenant Cluster, required if the worker nod
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-agent version:v0.28.6]<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-agent mode:DaemonSet version:v0.28.6]<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
@@ -39625,6 +39635,26 @@ unxpected ways. Only modify if you know what you are doing.<br/>
|
||||
<i>Default</i>: registry.k8s.io/kas-network-proxy/proxy-agent<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>mode</b></td>
|
||||
<td>enum</td>
|
||||
<td>
|
||||
Mode allows specifying the Agent deployment mode: Deployment, or DaemonSet (default).<br/>
|
||||
<br/>
|
||||
<i>Enum</i>: DaemonSet, Deployment<br/>
|
||||
<i>Default</i>: DaemonSet<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>replicas</b></td>
|
||||
<td>integer</td>
|
||||
<td>
|
||||
Replicas defines the number of replicas when Mode is Deployment.
|
||||
Must be 0 if Mode is DaemonSet.<br/>
|
||||
<br/>
|
||||
<i>Format</i>: int32<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b><a href="#tenantcontrolplanespecaddonskonnectivityagenttolerationsindex">tolerations</a></b></td>
|
||||
<td>[]object</td>
|
||||
@@ -40250,6 +40280,13 @@ KonnectivityStatus defines the status of Konnectivity as Addon.
|
||||
<i>Format</i>: date-time<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>mode</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>name</b></td>
|
||||
<td>string</td>
|
||||
|
||||
27
go.mod
27
go.mod
@@ -16,13 +16,13 @@ require (
|
||||
github.com/juju/mutex/v2 v2.0.0
|
||||
github.com/nats-io/nats.go v1.43.0
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/onsi/gomega v1.38.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/testcontainers/testcontainers-go v0.37.0
|
||||
github.com/testcontainers/testcontainers-go v0.38.0
|
||||
go.etcd.io/etcd/api/v3 v3.5.21
|
||||
go.etcd.io/etcd/client/v3 v3.5.21
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
@@ -65,7 +65,7 @@ require (
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
@@ -107,7 +107,6 @@ require (
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
@@ -129,7 +128,7 @@ require (
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
@@ -160,20 +159,20 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||
google.golang.org/grpc v1.68.1 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
51
go.sum
51
go.sum
@@ -64,8 +64,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
@@ -245,8 +245,8 @@ 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.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
|
||||
github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -279,8 +279,8 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
@@ -293,8 +293,9 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
@@ -315,8 +316,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
|
||||
github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
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=
|
||||
@@ -395,8 +396,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@@ -407,16 +408,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -429,15 +430,15 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -445,8 +446,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -461,8 +462,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
|
||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -46,6 +47,21 @@ func CreateKubeconfig(kubeconfigName string, ca CertificatePrivateKeyPair, confi
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
func IsKubeconfigCAValid(in, caCrt []byte) bool {
|
||||
kc, err := utilities.DecodeKubeconfigYAML(in)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, cluster := range kc.Clusters {
|
||||
if !bytes.Equal(cluster.Cluster.CertificateAuthorityData, caCrt) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsKubeconfigValid(bytes []byte) bool {
|
||||
kc, err := utilities.DecodeKubeconfigYAML(bytes)
|
||||
if err != nil {
|
||||
|
||||
@@ -117,7 +117,7 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: "x509",
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
},
|
||||
))
|
||||
|
||||
@@ -127,7 +127,9 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
return err
|
||||
}
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServer.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0 {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServer.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0) {
|
||||
isCAValid, err := crypto.VerifyCertificate(r.resource.Data[kubeadmconstants.APIServerCertName], secretCA.Data[kubeadmconstants.CACertName], x509.ExtKeyUsageServerAuth)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("certificate-authority verify failed: %s", err.Error()))
|
||||
@@ -170,6 +172,10 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
return err
|
||||
}
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.APIServerCertName: certificateKeyPair.Certificate,
|
||||
kubeadmconstants.APIServerKeyName: certificateKeyPair.PrivateKey,
|
||||
|
||||
@@ -104,7 +104,7 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: "x509",
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
},
|
||||
))
|
||||
|
||||
@@ -114,7 +114,9 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
return err
|
||||
}
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0 {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0) {
|
||||
isCAValid, err := crypto.VerifyCertificate(r.resource.Data[kubeadmconstants.APIServerKubeletClientCertName], secretCA.Data[kubeadmconstants.CACertName], x509.ExtKeyUsageClientAuth)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("certificate-authority verify failed: %s", err.Error()))
|
||||
@@ -152,6 +154,10 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
return err
|
||||
}
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.APIServerKubeletClientCertName: certificateKeyPair.Certificate,
|
||||
kubeadmconstants.APIServerKubeletClientKeyName: certificateKeyPair.PrivateKey,
|
||||
|
||||
@@ -96,7 +96,9 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
return func() error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.CA.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0 {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.CA.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0) {
|
||||
isValid, err := crypto.CheckCertificateAndPrivateKeyPairValidity(
|
||||
r.resource.Data[kubeadmconstants.CACertName],
|
||||
r.resource.Data[kubeadmconstants.CAKeyName],
|
||||
@@ -116,6 +118,10 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
}
|
||||
}
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
if tenantControlPlane.Status.Kubernetes.Version.Status != nil && *tenantControlPlane.Status.Kubernetes.Version.Status != kamajiv1alpha1.VersionProvisioning {
|
||||
r.isRotatingCA = true
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
return func() error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if r.DataStore.Spec.TLSConfig != nil {
|
||||
ca, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.Client)
|
||||
if err != nil {
|
||||
@@ -104,7 +106,7 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: "x509",
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
},
|
||||
))
|
||||
|
||||
@@ -116,7 +118,7 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
|
||||
if utilities.GetObjectChecksum(r.resource) == utilities.CalculateMapChecksum(r.resource.Data) {
|
||||
if r.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
|
||||
if isValid, _ := crypto.IsValidCertificateKeyPairBytes(r.resource.Data["server.crt"], r.resource.Data["server.key"]); isValid {
|
||||
if isValid, _ := crypto.IsValidCertificateKeyPairBytes(r.resource.Data["server.crt"], r.resource.Data["server.key"]); isValid && !isRotationRequested {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -174,6 +176,10 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
r.resource.Data = map[string][]byte{}
|
||||
}
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,7 +6,6 @@ package datastore
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
@@ -125,19 +124,6 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
|
||||
default:
|
||||
password = []byte(uuid.New().String())
|
||||
}
|
||||
// the coalesce function prioritizes the return value stored in the TenantControlPlane status,
|
||||
// although this is going to be populated by the UpdateTenantControlPlaneStatus handler of the resource datastore-setup:
|
||||
// the default value will be used for fresh new configurations, and preserving a previous one:
|
||||
// this will keep us safe from naming changes cases as occurred with the following commit:
|
||||
// https://github.com/clastix/kamaji/pull/203/commits/09ce38f489cccca72ab728a259bc8fb2cf6e4770
|
||||
coalesceFn := func(fromStatus string) []byte {
|
||||
if len(fromStatus) > 0 {
|
||||
return []byte(fromStatus)
|
||||
}
|
||||
// The dash character (-) must be replaced with an underscore, PostgreSQL is complaining about it:
|
||||
// https://github.com/clastix/kamaji/issues/328
|
||||
return []byte(strings.ReplaceAll(fmt.Sprintf("%s_%s", tenantControlPlane.GetNamespace(), tenantControlPlane.GetName()), "-", "_"))
|
||||
}
|
||||
|
||||
finalizersList := sets.New[string](r.resource.GetFinalizers()...)
|
||||
finalizersList.Insert(finalizers.DatastoreSecretFinalizer)
|
||||
@@ -161,7 +147,25 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
|
||||
username = u
|
||||
password = p
|
||||
} else {
|
||||
username = coalesceFn(tenantControlPlane.Status.Storage.Setup.User)
|
||||
// prioritize the username stored in the TenantControlPlane status,
|
||||
// although this is going to be populated by the UpdateTenantControlPlaneStatus handler of the resource datastore-setup:
|
||||
// the default value will be used for fresh new configurations, and preserving a previous one:
|
||||
// this will keep us safe from naming changes cases as occurred with the following commit:
|
||||
// https://github.com/clastix/kamaji/pull/203/commits/09ce38f489cccca72ab728a259bc8fb2cf6e4770
|
||||
switch {
|
||||
case len(tenantControlPlane.Status.Storage.Setup.User) > 0:
|
||||
// for existing TCPs, the dataStoreSchema will be adopted from the status,
|
||||
// as the mutating webhook only takes care of TCP creations, not updates
|
||||
username = []byte(tenantControlPlane.Status.Storage.Setup.User)
|
||||
tenantControlPlane.Spec.DataStoreUsername = string(username)
|
||||
case len(tenantControlPlane.Spec.DataStoreUsername) > 0:
|
||||
// for new TCPs, the spec field will have been provided by the user
|
||||
// or defaulted by the defaulting webhook
|
||||
username = []byte(tenantControlPlane.Spec.DataStoreUsername)
|
||||
default:
|
||||
// this can only happen on TCP creations when the webhook is not installed
|
||||
return fmt.Errorf("cannot build datastore storage config, username must either exist in Spec or Status")
|
||||
}
|
||||
}
|
||||
|
||||
var dataStoreSchema string
|
||||
|
||||
@@ -60,16 +60,17 @@ var _ = Describe("DatastoreStorageConfig", func() {
|
||||
}
|
||||
})
|
||||
|
||||
When("TCP has no dataStoreSchema defined", func() {
|
||||
When("TCP has neither dataStoreSchema nor dataStoreUsername defined", func() {
|
||||
It("should return an error", func() {
|
||||
_, err := resources.Handle(ctx, dsc, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
When("TCP has dataStoreSchema set in spec", func() {
|
||||
When("TCP has dataStoreSchema and dataStoreUsername set in spec", func() {
|
||||
BeforeEach(func() {
|
||||
tcp.Spec.DataStoreSchema = "custom-prefix"
|
||||
tcp.Spec.DataStoreUsername = "custom-user"
|
||||
})
|
||||
|
||||
It("should create the datastore secret with the schema name from the spec", func() {
|
||||
@@ -81,10 +82,11 @@ var _ = Describe("DatastoreStorageConfig", func() {
|
||||
Expect(fakeClient.List(ctx, secrets)).To(Succeed())
|
||||
Expect(secrets.Items).To(HaveLen(1))
|
||||
Expect(secrets.Items[0].Data["DB_SCHEMA"]).To(Equal([]byte("custom-prefix")))
|
||||
Expect(secrets.Items[0].Data["DB_USER"]).To(Equal([]byte("custom-user")))
|
||||
})
|
||||
})
|
||||
|
||||
When("TCP has dataStoreSchema set in status, but not in spec", func() {
|
||||
When("TCP has dataStoreSchema and dataStoreUsername set in status, but not in spec", func() {
|
||||
// this test case ensures that existing TCPs (created in a CRD version without
|
||||
// the dataStoreSchema field) correctly adopt the spec field from the status.
|
||||
|
||||
@@ -92,6 +94,7 @@ var _ = Describe("DatastoreStorageConfig", func() {
|
||||
By("updating the TCP status")
|
||||
Expect(fakeClient.Get(ctx, client.ObjectKeyFromObject(tcp), tcp)).To(Succeed())
|
||||
tcp.Status.Storage.Setup.Schema = "existing-schema-name"
|
||||
tcp.Status.Storage.Setup.User = "existing-username"
|
||||
Expect(fakeClient.Status().Update(ctx, tcp)).To(Succeed())
|
||||
|
||||
By("handling the resource")
|
||||
@@ -104,12 +107,14 @@ var _ = Describe("DatastoreStorageConfig", func() {
|
||||
Expect(fakeClient.List(ctx, secrets)).To(Succeed())
|
||||
Expect(secrets.Items).To(HaveLen(1))
|
||||
Expect(secrets.Items[0].Data["DB_SCHEMA"]).To(Equal([]byte("existing-schema-name")))
|
||||
Expect(secrets.Items[0].Data["DB_USER"]).To(Equal([]byte("existing-username")))
|
||||
|
||||
By("checking the TCP spec")
|
||||
// we have to check the modified struct here (instead of retrieving the object
|
||||
// via the fakeClient), as the TCP resource update is not done by the resources.
|
||||
// Instead, the TCP controller will handle TCP updates after handling all resources
|
||||
tcp.Spec.DataStoreSchema = "existing-schema-name"
|
||||
tcp.Spec.DataStoreUsername = "existing-username"
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -104,7 +104,7 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: "x509",
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
},
|
||||
))
|
||||
|
||||
@@ -114,7 +114,9 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
return err
|
||||
}
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0 {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0) {
|
||||
isCAValid, err := crypto.VerifyCertificate(r.resource.Data[kubeadmconstants.FrontProxyClientCertName], secretCA.Data[kubeadmconstants.FrontProxyCACertName], x509.ExtKeyUsageClientAuth)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("certificate-authority verify failed: %s", err.Error()))
|
||||
@@ -152,6 +154,10 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
return err
|
||||
}
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.FrontProxyClientCertName: certificateKeyPair.Certificate,
|
||||
kubeadmconstants.FrontProxyClientKeyName: certificateKeyPair.PrivateKey,
|
||||
|
||||
@@ -89,7 +89,9 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
|
||||
return func() error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0 {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0) {
|
||||
isValid, err := crypto.CheckCertificateAndPrivateKeyPairValidity(
|
||||
r.resource.Data[kubeadmconstants.FrontProxyCACertName],
|
||||
r.resource.Data[kubeadmconstants.FrontProxyCAKeyName],
|
||||
@@ -123,6 +125,10 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
type Agent struct {
|
||||
resource *appsv1.DaemonSet
|
||||
resource client.Object
|
||||
Client client.Client
|
||||
tenantClient client.Client
|
||||
}
|
||||
@@ -38,7 +38,8 @@ func (r *Agent) GetHistogram() prometheus.Histogram {
|
||||
|
||||
func (r *Agent) ShouldStatusBeUpdated(_ context.Context, tcp *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tcp.Spec.Addons.Konnectivity == nil && (tcp.Status.Addons.Konnectivity.Agent.Namespace != "" || tcp.Status.Addons.Konnectivity.Agent.Name != "") ||
|
||||
tcp.Spec.Addons.Konnectivity != nil && (tcp.Status.Addons.Konnectivity.Agent.Namespace != r.resource.Namespace || tcp.Status.Addons.Konnectivity.Agent.Name != r.resource.Name)
|
||||
tcp.Spec.Addons.Konnectivity != nil && (tcp.Status.Addons.Konnectivity.Agent.Namespace != r.resource.GetNamespace() || tcp.Status.Addons.Konnectivity.Agent.Name != r.resource.GetName()) ||
|
||||
tcp.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Mode != tcp.Status.Addons.Konnectivity.Agent.Mode
|
||||
}
|
||||
|
||||
func (r *Agent) ShouldCleanup(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -78,13 +79,20 @@ func (r *Agent) CleanUp(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlan
|
||||
func (r *Agent) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (err error) {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
r.resource = &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: AgentName,
|
||||
Namespace: AgentNamespace,
|
||||
},
|
||||
switch tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Mode {
|
||||
case kamajiv1alpha1.KonnectivityAgentModeDaemonSet:
|
||||
r.resource = &appsv1.DaemonSet{}
|
||||
case kamajiv1alpha1.KonnectivityAgentModeDeployment:
|
||||
r.resource = &appsv1.Deployment{}
|
||||
default:
|
||||
logger.Info("TenantControlPlane CRD is not updated, or validation failed, fallback to DaemonSet")
|
||||
|
||||
r.resource = &appsv1.DaemonSet{}
|
||||
}
|
||||
|
||||
r.resource.SetNamespace(AgentNamespace)
|
||||
r.resource.SetName(AgentName)
|
||||
|
||||
if r.tenantClient, err = utilities.GetTenantClient(ctx, r.Client, tenantControlPlane); err != nil {
|
||||
logger.Error(err, "unable to retrieve the Tenant Control Plane client")
|
||||
|
||||
@@ -96,7 +104,33 @@ func (r *Agent) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
|
||||
func (r *Agent) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
if tenantControlPlane.Spec.Addons.Konnectivity != nil {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
or, err := controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Mode == kamajiv1alpha1.KonnectivityAgentModeDaemonSet &&
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Agent.Mode != kamajiv1alpha1.KonnectivityAgentModeDaemonSet:
|
||||
var obj appsv1.Deployment
|
||||
obj.SetName(r.resource.GetName())
|
||||
obj.SetNamespace(r.resource.GetNamespace())
|
||||
|
||||
if cleanupErr := r.tenantClient.Delete(ctx, &obj); cleanupErr != nil {
|
||||
log.FromContext(ctx, "resource", r.GetName()).Error(cleanupErr, "cannot cleanup older appsv1.Deployment")
|
||||
}
|
||||
case tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Mode == kamajiv1alpha1.KonnectivityAgentModeDeployment &&
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Agent.Mode != kamajiv1alpha1.KonnectivityAgentModeDeployment:
|
||||
var obj appsv1.DaemonSet
|
||||
obj.SetName(r.resource.GetName())
|
||||
obj.SetNamespace(r.resource.GetNamespace())
|
||||
|
||||
if cleanupErr := r.tenantClient.Delete(ctx, &obj); cleanupErr != nil {
|
||||
log.FromContext(ctx, "resource", r.GetName()).Error(cleanupErr, "cannot cleanup older appsv1.DaemonSet")
|
||||
}
|
||||
}
|
||||
|
||||
return or, nil
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
@@ -107,13 +141,16 @@ func (r *Agent) GetName() string {
|
||||
}
|
||||
|
||||
func (r *Agent) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Agent = kamajiv1alpha1.ExternalKubernetesObjectStatus{}
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Agent = kamajiv1alpha1.KonnectivityAgentStatus{}
|
||||
|
||||
if tenantControlPlane.Spec.Addons.Konnectivity != nil {
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Agent = kamajiv1alpha1.ExternalKubernetesObjectStatus{
|
||||
Name: r.resource.GetName(),
|
||||
Namespace: r.resource.GetNamespace(),
|
||||
LastUpdate: metav1.Now(),
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Agent = kamajiv1alpha1.KonnectivityAgentStatus{
|
||||
ExternalKubernetesObjectStatus: kamajiv1alpha1.ExternalKubernetesObjectStatus{
|
||||
Name: r.resource.GetName(),
|
||||
Namespace: r.resource.GetNamespace(),
|
||||
LastUpdate: metav1.Now(),
|
||||
},
|
||||
Mode: tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Mode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,27 +170,31 @@ func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
if r.resource.Spec.Selector == nil {
|
||||
r.resource.Spec.Selector = &metav1.LabelSelector{}
|
||||
}
|
||||
r.resource.Spec.Selector.MatchLabels = map[string]string{
|
||||
"k8s-app": AgentName,
|
||||
}
|
||||
|
||||
r.resource.Spec.Template.SetLabels(utilities.MergeMaps(
|
||||
r.resource.Spec.Template.GetLabels(),
|
||||
map[string]string{
|
||||
specSelector := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"k8s-app": AgentName,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
r.resource.Spec.Template.Spec.PriorityClassName = "system-cluster-critical"
|
||||
r.resource.Spec.Template.Spec.Tolerations = tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Tolerations
|
||||
r.resource.Spec.Template.Spec.NodeSelector = map[string]string{
|
||||
var podTemplateSpec *corev1.PodTemplateSpec
|
||||
|
||||
switch obj := r.resource.(type) {
|
||||
case *appsv1.DaemonSet:
|
||||
obj.Spec.Selector = specSelector
|
||||
podTemplateSpec = &obj.Spec.Template
|
||||
case *appsv1.Deployment:
|
||||
obj.Spec.Selector = specSelector
|
||||
podTemplateSpec = &obj.Spec.Template
|
||||
}
|
||||
|
||||
podTemplateSpec.SetLabels(utilities.MergeMaps(podTemplateSpec.GetLabels(), specSelector.MatchLabels))
|
||||
podTemplateSpec.Spec.PriorityClassName = "system-cluster-critical"
|
||||
podTemplateSpec.Spec.Tolerations = tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Tolerations
|
||||
podTemplateSpec.Spec.NodeSelector = map[string]string{
|
||||
"kubernetes.io/os": "linux",
|
||||
}
|
||||
r.resource.Spec.Template.Spec.ServiceAccountName = AgentName
|
||||
r.resource.Spec.Template.Spec.Volumes = []corev1.Volume{
|
||||
podTemplateSpec.Spec.ServiceAccountName = AgentName
|
||||
podTemplateSpec.Spec.Volumes = []corev1.Volume{
|
||||
{
|
||||
Name: agentTokenName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
@@ -173,13 +214,13 @@ func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
},
|
||||
}
|
||||
|
||||
if len(r.resource.Spec.Template.Spec.Containers) != 1 {
|
||||
r.resource.Spec.Template.Spec.Containers = make([]corev1.Container, 1)
|
||||
if len(podTemplateSpec.Spec.Containers) != 1 {
|
||||
podTemplateSpec.Spec.Containers = make([]corev1.Container, 1)
|
||||
}
|
||||
|
||||
r.resource.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("%s:%s", tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Image, tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Version)
|
||||
r.resource.Spec.Template.Spec.Containers[0].Name = AgentName
|
||||
r.resource.Spec.Template.Spec.Containers[0].Command = []string{"/proxy-agent"}
|
||||
podTemplateSpec.Spec.Containers[0].Image = fmt.Sprintf("%s:%s", tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Image, tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Version)
|
||||
podTemplateSpec.Spec.Containers[0].Name = AgentName
|
||||
podTemplateSpec.Spec.Containers[0].Command = []string{"/proxy-agent"}
|
||||
|
||||
args := make(map[string]string)
|
||||
args["-v"] = "8"
|
||||
@@ -197,18 +238,18 @@ func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
args[k] = v
|
||||
}
|
||||
|
||||
r.resource.Spec.Template.Spec.Containers[0].Args = utilities.ArgsFromMapToSlice(args)
|
||||
r.resource.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
|
||||
podTemplateSpec.Spec.Containers[0].Args = utilities.ArgsFromMapToSlice(args)
|
||||
podTemplateSpec.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
|
||||
{
|
||||
MountPath: "/var/run/secrets/tokens",
|
||||
Name: agentTokenName,
|
||||
},
|
||||
}
|
||||
r.resource.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{
|
||||
podTemplateSpec.Spec.Containers[0].LivenessProbe = &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(8134),
|
||||
Port: intstr.FromInt32(8134),
|
||||
Scheme: corev1.URISchemeHTTP,
|
||||
},
|
||||
},
|
||||
@@ -219,6 +260,16 @@ func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
FailureThreshold: 3,
|
||||
}
|
||||
|
||||
switch tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Mode {
|
||||
case kamajiv1alpha1.KonnectivityAgentModeDaemonSet:
|
||||
r.resource.(*appsv1.DaemonSet).Spec.Template = *podTemplateSpec //nolint:forcetypeassert
|
||||
case kamajiv1alpha1.KonnectivityAgentModeDeployment:
|
||||
//nolint:forcetypeassert
|
||||
r.resource.(*appsv1.Deployment).Spec.Template = *podTemplateSpec
|
||||
//nolint:forcetypeassert
|
||||
r.resource.(*appsv1.Deployment).Spec.Replicas = pointer.To(tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Replicas)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: "x509",
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
},
|
||||
))
|
||||
|
||||
@@ -114,7 +114,9 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
|
||||
return err
|
||||
}
|
||||
|
||||
if checksum := tenantControlPlane.Status.Addons.Konnectivity.Certificate.Checksum; len(checksum) > 0 && checksum == utilities.CalculateMapChecksum(r.resource.Data) {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Addons.Konnectivity.Certificate.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.CalculateMapChecksum(r.resource.Data)) {
|
||||
isValid, err := crypto.IsValidCertificateKeyPairBytes(r.resource.Data[corev1.TLSCertKey], r.resource.Data[corev1.TLSPrivateKeyKey])
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", konnectivityCertAndKeyBaseName, err.Error()))
|
||||
@@ -145,6 +147,10 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
|
||||
return err
|
||||
}
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
r.resource.Type = corev1.SecretTypeTLS
|
||||
r.resource.Data = map[string][]byte{
|
||||
corev1.TLSCertKey: cert.Bytes(),
|
||||
|
||||
@@ -103,7 +103,7 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: "kubeconfig",
|
||||
constants.ControllerLabelResource: utilities.CertificateKubeconfigLabel,
|
||||
},
|
||||
))
|
||||
|
||||
@@ -113,7 +113,10 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
|
||||
return err
|
||||
}
|
||||
|
||||
if checksum := tenantControlPlane.Status.Addons.Konnectivity.Certificate.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
checksum := tenantControlPlane.Status.Addons.Konnectivity.Kubeconfig.Checksum
|
||||
if len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) && !isRotationRequested {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -181,6 +184,8 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
|
||||
konnectivityKubeconfigFileName: kubeconfigBytes,
|
||||
}
|
||||
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -172,10 +172,10 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: "kubeconfig",
|
||||
constants.ControllerLabelResource: utilities.CertificateKubeconfigLabel,
|
||||
},
|
||||
))
|
||||
r.resource.SetAnnotations(map[string]string{constants.Checksum: checksum})
|
||||
r.resource.SetAnnotations(utilities.MergeMaps(r.resource.GetAnnotations(), map[string]string{constants.Checksum: checksum}))
|
||||
|
||||
if err = ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
|
||||
logger.Error(err, "cannot set controller reference", "resource", r.GetName())
|
||||
@@ -185,18 +185,21 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
|
||||
|
||||
var shouldCreate bool
|
||||
|
||||
shouldCreate = shouldCreate || r.resource.Data == nil // Missing data key
|
||||
shouldCreate = shouldCreate || len(r.resource.Data) == 0 // Missing data key
|
||||
shouldCreate = shouldCreate || len(r.resource.Data[r.KubeConfigFileName]) == 0 // Missing kubeconfig file, must be generated
|
||||
shouldCreate = shouldCreate || r.resource.Data == nil // Missing data key
|
||||
shouldCreate = shouldCreate || len(r.resource.Data) == 0 // Missing data key
|
||||
shouldCreate = shouldCreate || len(r.resource.Data[r.KubeConfigFileName]) == 0 // Missing kubeconfig file, must be generated
|
||||
shouldCreate = shouldCreate || !kubeadm.IsKubeconfigCAValid(r.resource.Data[r.KubeConfigFileName], caCertificatesSecret.Data[kubeadmconstants.CACertName])
|
||||
shouldCreate = shouldCreate || !kubeadm.IsKubeconfigValid(r.resource.Data[r.KubeConfigFileName]) // invalid kubeconfig, or expired client certificate
|
||||
shouldCreate = shouldCreate || status.Checksum != checksum || len(r.resource.UID) == 0 // Wrong checksum
|
||||
|
||||
shouldRotate := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if !shouldCreate {
|
||||
v, ok := r.resource.Data[r.KubeConfigFileName]
|
||||
shouldCreate = len(v) == 0 || !ok
|
||||
}
|
||||
//nolint:nestif
|
||||
if shouldCreate {
|
||||
|
||||
if shouldCreate || shouldRotate {
|
||||
crtKeyPair := kubeadm.CertificatePrivateKeyPair{
|
||||
Certificate: caCertificatesSecret.Data[kubeadmconstants.CACertName],
|
||||
PrivateKey: caCertificatesSecret.Data[kubeadmconstants.CAKeyName],
|
||||
@@ -213,6 +216,10 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
|
||||
return kcErr
|
||||
}
|
||||
|
||||
if shouldRotate {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
r.resource.Data[r.KubeConfigFileName] = kubeconfig
|
||||
// Adding a kubeconfig useful for the local connections:
|
||||
// especially for the admin.conf and super-admin.conf, these would use the public IP address.
|
||||
|
||||
@@ -91,7 +91,9 @@ func (r *SACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
return func() error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.SA.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0 {
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.SA.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) || len(r.resource.UID) > 0) {
|
||||
isValid, err := crypto.CheckPublicAndPrivateKeyValidity(r.resource.Data[kubeadmconstants.ServiceAccountPublicKeyName], r.resource.Data[kubeadmconstants.ServiceAccountPrivateKeyName])
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("%s public_key-private_key pair is not valid: %s", kubeadmconstants.ServiceAccountKeyBaseName, err.Error()))
|
||||
@@ -122,6 +124,10 @@ func (r *SACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
}
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
|
||||
40
internal/utilities/certs_rotation.go
Normal file
40
internal/utilities/certs_rotation.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package utilities
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
RotateCertificateRequestAnnotation = "certs.kamaji.clastix.io/rotate"
|
||||
|
||||
CertificateX509Label = "x509"
|
||||
CertificateKubeconfigLabel = "kubeconfig"
|
||||
)
|
||||
|
||||
func IsRotationRequested(obj client.Object) bool {
|
||||
if obj.GetAnnotations() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
v, ok := obj.GetAnnotations()[RotateCertificateRequestAnnotation]
|
||||
if ok && v == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func SetLastRotationTimestamp(obj client.Object) {
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
|
||||
annotations[RotateCertificateRequestAnnotation] = metav1.Now().String()
|
||||
|
||||
obj.SetAnnotations(annotations)
|
||||
}
|
||||
@@ -76,4 +76,11 @@ func (t TenantControlPlaneDefaults) defaultUnsetFields(tcp *kamajiv1alpha1.Tenan
|
||||
dss := strings.ReplaceAll(fmt.Sprintf("%s_%s", tcp.GetNamespace(), tcp.GetName()), "-", "_")
|
||||
tcp.Spec.DataStoreSchema = dss
|
||||
}
|
||||
|
||||
if len(tcp.Spec.DataStoreUsername) == 0 {
|
||||
// The dash character (-) must be replaced with an underscore, PostgreSQL is complaining about it:
|
||||
// https://github.com/clastix/kamaji/issues/328
|
||||
username := strings.ReplaceAll(fmt.Sprintf("%s_%s", tcp.GetNamespace(), tcp.GetName()), "-", "_")
|
||||
tcp.Spec.DataStoreUsername = username
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ var _ = Describe("TCP Defaulting Webhook", func() {
|
||||
It("should issue all required patches", func() {
|
||||
ops, err := t.OnCreate(tcp)(ctx, admission.Request{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ops).To(HaveLen(3))
|
||||
Expect(ops).To(HaveLen(4))
|
||||
})
|
||||
|
||||
It("should default the dataStore", func() {
|
||||
@@ -60,12 +60,15 @@ var _ = Describe("TCP Defaulting Webhook", func() {
|
||||
))
|
||||
})
|
||||
|
||||
It("should default the dataStoreSchema to the expected value", func() {
|
||||
It("should default the dataStoreSchema and dataStoreUsername to the expected value", func() {
|
||||
ops, err := t.OnCreate(tcp)(ctx, admission.Request{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ops).To(ContainElement(
|
||||
jsonpatch.Operation{Operation: "add", Path: "/spec/dataStoreSchema", Value: "default_tcp"},
|
||||
))
|
||||
Expect(ops).To(ContainElement(
|
||||
jsonpatch.Operation{Operation: "add", Path: "/spec/dataStoreUsername", Value: "default_tcp"},
|
||||
))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -73,6 +76,7 @@ var _ = Describe("TCP Defaulting Webhook", func() {
|
||||
BeforeEach(func() {
|
||||
tcp.Spec.DataStore = "etcd"
|
||||
tcp.Spec.DataStoreSchema = "my_tcp"
|
||||
tcp.Spec.DataStoreUsername = "my_tcp"
|
||||
tcp.Spec.ControlPlane.Deployment.Replicas = ptr.To(int32(2))
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user