Compare commits

...

36 Commits

Author SHA1 Message Date
Dario Tranchitella
e34fc1851f chore(helm): releasing v0.3.3 2023-08-08 12:07:30 +02:00
Dario Tranchitella
740fe9c938 chore(kustomize): releasing v0.3.3 2023-08-08 12:07:30 +02:00
Dario Tranchitella
65854721de fix(ingress): referencing ingress port from hostname 2023-08-08 10:55:33 +02:00
geoffrey1330
adde828e03 docs: added default label to TCP resources 2023-08-08 09:56:54 +02:00
Dario Tranchitella
ffc2c7c967 fix(gh): triggering e2e upon cmd changes 2023-08-03 18:04:07 +02:00
Dario Tranchitella
0f195286a7 docs(manager): cache resync period 2023-08-03 18:04:07 +02:00
Dario Tranchitella
f768f93fe9 feat: cache resync period 2023-08-03 18:04:07 +02:00
Dario Tranchitella
05cbff1fd8 docs: kubeconfig and certificates rotation 2023-08-03 18:03:54 +02:00
Dario Tranchitella
7e94ecdbab feat: kubeconfig and certificates rotation 2023-08-03 18:03:54 +02:00
Dario Tranchitella
648da19687 refactor: checking kubeconfig user certs validity 2023-08-03 18:03:54 +02:00
Dario Tranchitella
6c4b339c4b fix(typo): error message for kubeconfig 2023-08-03 18:03:54 +02:00
Dario Tranchitella
eee62032de refactor: ensuring owner reference and labels with controller label 2023-08-03 18:03:54 +02:00
Dario Tranchitella
8e8ee92fb2 docs: releasing v0.3.2 2023-08-01 19:11:32 +02:00
Dario Tranchitella
f3be9e5442 chore(helm): releasing v0.3.2 2023-08-01 19:11:32 +02:00
Dario Tranchitella
fb296267f6 chore(kustomize): releasing v0.3.2 2023-08-01 19:11:32 +02:00
Dario Tranchitella
751ce3722b fix(capi): keys for kubeadm-bootstrap controller 2023-08-01 19:04:58 +02:00
Dario Tranchitella
d99ffb0334 chore(samples): wrong name 2023-08-01 13:51:09 +02:00
Dario Tranchitella
f831f385c4 feat(cli): controller reconcile timeout flag with 30s default value 2023-08-01 13:51:09 +02:00
Dario Tranchitella
f301c9bdc2 fix(scheme): must register defaulter funcs 2023-07-27 19:25:42 +02:00
Thomas Güttler
0909529e6b fix(docs): typos 2023-07-12 10:33:33 +02:00
Dario Tranchitella
d0aacd03f6 chore(helm): releasing v0.3.1 2023-07-07 16:12:21 +02:00
Dario Tranchitella
f4c84946c0 chore(kustomize): releasing v0.3.1 2023-07-07 16:12:21 +02:00
Dario Tranchitella
2c72369b99 chore: releasing v0.3.1 2023-07-07 16:12:21 +02:00
Dario Tranchitella
abcc662c96 fix(datastore): replacing dash with underscore 2023-07-05 22:20:55 +02:00
Dario Tranchitella
792119d2d3 fix: validating tcp name 2023-07-04 21:55:19 +02:00
daseulcho
f0e675dea3 fix(kubelet-config): adding versioned kubelet config 2023-07-04 18:19:34 +02:00
daseulcho
4413061640 fix(kubelet-config): adding versioned kubelet config 2023-07-04 09:23:36 +02:00
Dario Tranchitella
8f57ff407e fix(konnectivity): setting service nodeport
Co-authored-by: jds <jds9090@kinx.net>
2023-07-04 07:19:37 +02:00
Dario Tranchitella
94f2d9074d refactor: unrequired node registration for kubeadm config 2023-07-03 15:28:12 +02:00
Dario Tranchitella
fadcc219ec docs: kubernetes 1.27.3 support 2023-07-01 00:01:32 +02:00
Dario Tranchitella
af5ac4acab feat: kubernetes 1.27.3 support 2023-07-01 00:01:32 +02:00
Dario Tranchitella
069afd9b17 fix(kubeconfig): recreating kubeconfig upon checksum failure 2023-06-30 16:07:59 +02:00
Dario Tranchitella
6741194034 fix(gh): missing build args for docker-ci 2023-06-30 10:53:11 +02:00
Dario Tranchitella
7acba20056 fix(webhook): wrong object for migrate route 2023-06-30 10:52:52 +02:00
Dario Tranchitella
0d2cf784f5 docs: capi support 2023-06-22 16:18:29 +02:00
bsctl
4db8230912 chore(helm): update metadata 2023-06-14 06:21:59 +00:00
52 changed files with 768 additions and 166 deletions

View File

@@ -12,6 +12,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Generate build-args
id: build-args
@@ -85,7 +87,13 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args:
build-args: |
GIT_LAST_TAG=${{ env.GIT_LAST_TAG }}
GIT_HEAD_COMMIT=${{ env.GIT_HEAD_COMMIT }}
GIT_TAG_COMMIT=${{ env.GIT_TAG_COMMIT }}
GIT_MODIFIED=${{ env.GIT_MODIFIED }}
GIT_REPO=${{ env.GIT_REPO }}
BUILD_DATE=${{ env.BUILD_DATE }}
- name: Image digest
run: echo ${{ steps.build-release.outputs.digest }}

View File

@@ -13,6 +13,7 @@ on:
- 'main.go'
- 'Makefile'
- 'internal/**'
- 'cmd/**'
pull_request:
branches: [ "*" ]
paths:
@@ -25,6 +26,7 @@ on:
- 'main.go'
- 'Makefile'
- 'internal/**'
- 'cmd/**'
jobs:
kind:

View File

@@ -3,7 +3,7 @@
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
VERSION ?= 0.3.0
VERSION ?= 0.3.3
# CHANNELS define the bundle channels used in the bundle.
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")

View File

@@ -35,7 +35,7 @@
- [x] Seamless migration between datastores
- [ ] Automatic assignment to a datastore
- [ ] Autoscaling of Tenant Control Plane
- [ ] Provisioning through Cluster APIs
- [x] Provisioning through Cluster APIs
- [ ] Terraform provider
- [ ] Custom Prometheus metrics for monitoring and alerting

BIN
assets/logo-colored.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,10 +1,8 @@
apiVersion: v2
appVersion: v0.3.0
description: Kamaji is a tool aimed to build and operate a Managed Kubernetes Service
with a fraction of the operational burden. With Kamaji, you can deploy and operate
hundreds of Kubernetes clusters as a hyper-scaler.
appVersion: v0.3.3
description: Kamaji deploys and operates Kubernetes at scale with a fraction of the operational burden. Kamaji turns any Kubernetes cluster into an “admin cluster” to orchestrate other Kubernetes clusters called “tenant clusters”. Kamaji is special because the Control Plane components are running in a single pod instead of dedicated machines. This solution makes running multiple Control Planes cheaper and easier to deploy and operate.
home: https://github.com/clastix/kamaji
icon: https://github.com/clastix/kamaji/raw/master/assets/kamaji-logo.png
icon: https://github.com/clastix/kamaji/raw/master/assets/logo-colored.png
kubeVersion: ">=1.21.0-0"
maintainers:
- email: dario@tranchitella.eu
@@ -17,8 +15,8 @@ name: kamaji
sources:
- https://github.com/clastix/kamaji
type: application
version: 0.12.0
version: 0.12.4
annotations:
catalog.cattle.io/certified: partner
catalog.cattle.io/release-name: kamaji
catalog.cattle.io/display-name: Kamaji - Managed Kubernetes Service
catalog.cattle.io/display-name: Kamaji

View File

@@ -1,8 +1,8 @@
# kamaji
![Version: 0.12.0](https://img.shields.io/badge/Version-0.12.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.3.0](https://img.shields.io/badge/AppVersion-v0.3.0-informational?style=flat-square)
![Version: 0.12.4](https://img.shields.io/badge/Version-0.12.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.3.3](https://img.shields.io/badge/AppVersion-v0.3.3-informational?style=flat-square)
Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a fraction of the operational burden. With Kamaji, you can deploy and operate hundreds of Kubernetes clusters as a hyper-scaler.
Kamaji deploys and operates Kubernetes at scale with a fraction of the operational burden. Kamaji turns any Kubernetes cluster into an “admin cluster” to orchestrate other Kubernetes clusters called “tenant clusters”. Kamaji is special because the Control Plane components are running in a single pod instead of dedicated machines. This solution makes running multiple Control Planes cheaper and easier to deploy and operate.
## Maintainers

View File

@@ -1,30 +1,12 @@
# Kamaji - Managed Kubernetes Service
# Kamaji
Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a fraction of the operational burden.
Kamaji deploys and operates Kubernetes at scale with a fraction of the operational burden.
Useful links:
- [Kamaji Github repository](https://github.com/clastix/kamaji)
- [Kamaji Documentation](https://github.com/clastix/kamaji/docs/)
- [Kamaji Documentation](https://kamaji.clastix.io)
## Requirements
* Kubernetes v1.22+
* Helm v3
# Installation
To install the Chart with the release name `kamaji`:
helm upgrade --install --namespace kamaji-system --create-namespace clastix/kamaji
Show the status:
helm status kamaji -n kamaji-system
Upgrade the Chart
helm upgrade kamaji -n kamaji-system clastix/kamaji
Uninstall the Chart
helm uninstall kamaji -n kamaji-system
* Helm v3

View File

@@ -9,12 +9,15 @@ import (
"io"
"os"
goRuntime "runtime"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
@@ -30,21 +33,24 @@ import (
"github.com/clastix/kamaji/internal/webhook/routes"
)
//nolint:maintidx
func NewCmd(scheme *runtime.Scheme) *cobra.Command {
// CLI flags
var (
metricsBindAddress string
healthProbeBindAddress string
leaderElect bool
tmpDirectory string
kineImage string
datastore string
managerNamespace string
managerServiceAccountName string
managerServiceName string
webhookCABundle []byte
migrateJobImage string
maxConcurrentReconciles int
metricsBindAddress string
healthProbeBindAddress string
leaderElect bool
tmpDirectory string
kineImage string
controllerReconcileTimeout time.Duration
cacheResyncPeriod time.Duration
datastore string
managerNamespace string
managerServiceAccountName string
managerServiceName string
webhookCABundle []byte
migrateJobImage string
maxConcurrentReconciles int
webhookCAPath string
)
@@ -73,6 +79,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
return err
}
if controllerReconcileTimeout.Seconds() == 0 {
return fmt.Errorf("the controller reconcile timeout must be greater than zero")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
@@ -92,6 +102,11 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
LeaderElection: leaderElect,
LeaderElectionNamespace: managerNamespace,
LeaderElectionID: "799b98bc.clastix.io",
NewCache: func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
opts.Resync = &cacheResyncPeriod
return cache.New(config, opts)
},
})
if err != nil {
setupLog.Error(err, "unable to start manager")
@@ -99,7 +114,7 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
return err
}
tcpChannel := make(controllers.TenantControlPlaneChannel)
tcpChannel, certChannel := make(controllers.TenantControlPlaneChannel), make(controllers.CertificateChannel)
if err = (&controllers.DataStore{TenantControlPlaneTrigger: tcpChannel}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DataStore")
@@ -111,10 +126,12 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
Client: mgr.GetClient(),
APIReader: mgr.GetAPIReader(),
Config: controllers.TenantControlPlaneReconcilerConfig{
ReconcileTimeout: controllerReconcileTimeout,
DefaultDataStoreName: datastore,
KineContainerImage: kineImage,
TmpBaseDirectory: tmpDirectory,
},
CertificateChan: certChannel,
TriggerChan: tcpChannel,
KamajiNamespace: managerNamespace,
KamajiServiceAccount: managerServiceAccountName,
@@ -129,6 +146,12 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
return err
}
if err = (&controllers.CertificateLifecycle{Channel: certChannel}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CertificateLifecycle")
return err
}
if err = (&kamajiv1alpha1.DatastoreUsedSecret{}).SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create indexer", "indexer", "DatastoreUsedSecret")
@@ -149,6 +172,7 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
handlers.TenantControlPlaneDefaults{DefaultDatastore: datastore},
},
routes.TenantControlPlaneValidate{}: {
handlers.TenantControlPlaneName{},
handlers.TenantControlPlaneVersion{},
handlers.TenantControlPlaneKubeletAddresses{},
handlers.TenantControlPlaneDataStore{Client: mgr.GetClient()},
@@ -230,6 +254,8 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
cmd.Flags().StringVar(&managerServiceName, "webhook-service-name", "kamaji-webhook-service", "The Kamaji webhook server Service name which is used to get validation webhooks, required for the TenantControlPlane migration jobs.")
cmd.Flags().StringVar(&managerServiceAccountName, "serviceaccount-name", os.Getenv("SERVICE_ACCOUNT"), "The Kubernetes Namespace on which the Operator is running in, required for the TenantControlPlane migration jobs.")
cmd.Flags().StringVar(&webhookCAPath, "webhook-ca-path", "/tmp/k8s-webhook-server/serving-certs/ca.crt", "Path to the Manager webhook server CA, required for the TenantControlPlane migration jobs.")
cmd.Flags().DurationVar(&controllerReconcileTimeout, "controller-reconcile-timeout", 30*time.Second, "The reconciliation request timeout before the controller withdraw the external resource calls, such as dealing with the Datastore, or the Tenant Control Plane API endpoint.")
cmd.Flags().DurationVar(&cacheResyncPeriod, "cache-resync-period", 10*time.Hour, "The controller-runtime.Manager cache resync period.")
cobra.OnInitialize(func() {
viper.AutomaticEnv()

View File

@@ -12,6 +12,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
appsv1 "k8s.io/kubernetes/pkg/apis/apps/v1"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
@@ -26,6 +27,7 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(kamajiv1alpha1.AddToScheme(scheme))
utilruntime.Must(appsv1.RegisterDefaults(scheme))
},
}
}

View File

@@ -5062,7 +5062,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
image: clastix/kamaji:v0.3.0
image: clastix/kamaji:v0.3.3
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -13,4 +13,4 @@ kind: Kustomization
images:
- name: controller
newName: clastix/kamaji
newTag: v0.3.0
newTag: v0.3.3

View File

@@ -2,6 +2,8 @@ apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: k8s-126
labels:
tenant.clastix.io: k8s-126
spec:
controlPlane:
deployment:

View File

@@ -2,6 +2,8 @@ apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: additionalcontainers
labels:
tenant.clastix.io: additionalcontainers
spec:
dataStore: postgresql-bronze
controlPlane:

View File

@@ -2,6 +2,8 @@ apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: additional-volumes
labels:
tenant.clastix.io: additional-volumes
spec:
controlPlane:
deployment:

View File

@@ -2,6 +2,8 @@ apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: kine
labels:
tenant.clastix.io: kine
spec:
addons:
coreDNS: {}

View File

@@ -2,6 +2,8 @@ apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: konnectivity-addon
labels:
tenant.clastix.io: konnectivity-addon
spec:
deployment:
replicas: 2

View File

@@ -0,0 +1,10 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package controllers
import (
"sigs.k8s.io/controller-runtime/pkg/event"
)
type CertificateChannel chan event.GenericEvent

View File

@@ -0,0 +1,160 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package controllers
import (
"context"
"crypto/x509"
"fmt"
"time"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1"
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/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/utilities"
)
type CertificateLifecycle struct {
Channel CertificateChannel
client client.Client
}
func (s *CertificateLifecycle) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
logger := log.FromContext(ctx)
logger.Info("starting CertificateLifecycle handling")
secret := corev1.Secret{}
if err := s.client.Get(ctx, request.NamespacedName, &secret); err != nil {
if k8serrors.IsNotFound(err) {
logger.Info("resource may have been deleted, skipping")
return reconcile.Result{}, nil
}
}
checkType, ok := secret.GetLabels()[constants.ControllerLabelResource]
if !ok {
logger.Info("missing controller label, shouldn't happen")
return reconcile.Result{}, nil
}
var crt *x509.Certificate
var err error
switch checkType {
case "x509":
crt, err = s.extractCertificateFromBareSecret(secret)
case "kubeconfig":
crt, err = s.extractCertificateFromKubeconfig(secret)
default:
err = fmt.Errorf("unsupported strategy, %s", checkType)
}
if err != nil {
logger.Error(err, "skipping reconciliation")
return reconcile.Result{}, nil
}
deadline := time.Now().AddDate(0, 0, 1)
if deadline.After(crt.NotAfter) {
logger.Info("certificate near expiration, must be rotated")
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: secret.GetOwnerReferences()[0].Name,
Namespace: secret.Namespace,
},
}}
logger.Info("certificate rotation triggered")
return reconcile.Result{}, nil
}
after := crt.NotAfter.Sub(deadline)
logger.Info("certificate is still valid, enqueuing back", "after", after.String())
return reconcile.Result{Requeue: true, RequeueAfter: after}, nil
}
func (s *CertificateLifecycle) extractCertificateFromBareSecret(secret corev1.Secret) (*x509.Certificate, error) {
var crt *x509.Certificate
var err error
for _, v := range secret.Data {
if crt, err = crypto.ParseCertificateBytes(v); err == nil {
break
}
}
if crt == nil {
return nil, fmt.Errorf("none of the provided keys is containing a valid x509 certificate")
}
return crt, nil
}
func (s *CertificateLifecycle) extractCertificateFromKubeconfig(secret corev1.Secret) (*x509.Certificate, error) {
var kc *clientcmdapiv1.Config
var err error
for k := range secret.Data {
if kc, err = utilities.DecodeKubeconfig(secret, k); err == nil {
break
}
}
if kc == nil {
return nil, fmt.Errorf("none of the provided keys is containing a valid kubeconfig")
}
crt, err := crypto.ParseCertificateBytes(kc.AuthInfos[0].AuthInfo.ClientCertificateData)
if err != nil {
return nil, errors.Wrap(err, "cannot parse kubeconfig certificate bytes")
}
return crt, nil
}
func (s *CertificateLifecycle) SetupWithManager(mgr controllerruntime.Manager) error {
s.client = mgr.GetClient()
supportedStrategies := sets.New[string]("x509", "kubeconfig")
return controllerruntime.NewControllerManagedBy(mgr).
For(&corev1.Secret{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
labels := object.GetLabels()
if labels == nil {
return false
}
value, ok := labels[constants.ControllerLabelResource]
if !ok {
return false
}
return supportedStrategies.Has(value)
}))).
Complete(s)
}

View File

@@ -50,12 +50,17 @@ type TenantControlPlaneReconciler struct {
KamajiService string
KamajiMigrateImage string
MaxConcurrentReconciles int
// CertificateChan is the channel used by the CertificateLifecycleController that is checking for
// certificates and kubeconfig user certs validity: a generic event for the given TCP will be triggered
// once the validity threshold for the given certificate is reached.
CertificateChan CertificateChannel
clock mutex.Clock
}
// TenantControlPlaneReconcilerConfig gives the necessary configuration for TenantControlPlaneReconciler.
type TenantControlPlaneReconcilerConfig struct {
ReconcileTimeout time.Duration
DefaultDataStoreName string
KineContainerImage string
TmpBaseDirectory string
@@ -74,6 +79,10 @@ type TenantControlPlaneReconcilerConfig struct {
func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
var cancelFn context.CancelFunc
ctx, cancelFn = context.WithTimeout(ctx, r.Config.ReconcileTimeout)
defer cancelFn()
tenantControlPlane, err := r.getTenantControlPlane(ctx, req.NamespacedName)()
if err != nil {
if apimachineryerrors.IsNotFound(err) {
@@ -218,6 +227,14 @@ func (r *TenantControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error
r.clock = clock.RealClock{}
return ctrl.NewControllerManagedBy(mgr).
Watches(&source.Channel{Source: r.CertificateChan}, handler.Funcs{GenericFunc: func(genericEvent event.GenericEvent, limitingInterface workqueue.RateLimitingInterface) {
limitingInterface.AddRateLimited(ctrl.Request{
NamespacedName: k8stypes.NamespacedName{
Namespace: genericEvent.Object.GetNamespace(),
Name: genericEvent.Object.GetName(),
},
})
}}).
Watches(&source.Channel{Source: r.TriggerChan}, handler.Funcs{GenericFunc: func(genericEvent event.GenericEvent, limitingInterface workqueue.RateLimitingInterface) {
limitingInterface.AddRateLimited(ctrl.Request{
NamespacedName: k8stypes.NamespacedName{

View File

@@ -20,9 +20,13 @@ Kamaji offers a [Custom Resource Definition](https://kubernetes.io/docs/tasks/ex
All the _“tenant clusters”_ built with Kamaji are fully compliant CNCF Kubernetes clusters and are compatible with the standard Kubernetes toolchains everybody knows and loves. See [CNCF compliance](reference/conformance.md).
## Tenant worker nodes
And what about the tenant worker nodes? They are just _"worker nodes"_, i.e. regular virtual or bare metal machines, connecting to the APIs server of the Tenant Control Plane. Kamaji's goal is to manage the lifecycle of hundreds of these _“tenant clusters”_, not only one, so how to add another tenant cluster to Kamaji? As you could expect, you have just deploys a new Tenant Control Plane in one of the _“admin cluster”_ namespace, and then joins the tenant worker nodes to it.
We have in roadmap, the Cluster APIs support as well as a Terraform provider so that you can create _“tenant clusters”_ in a declarative way.
And what about the tenant worker nodes?
They are just _"worker nodes"_, i.e. regular virtual or bare metal machines, connecting to the APIs server of the Tenant Control Plane.
Kamaji's goal is to manage the lifecycle of hundreds of these _“tenant clusters”_, not only one, so how to add another tenant cluster to Kamaji?
As you could expect, you have just deploys a new Tenant Control Plane in one of the _“admin cluster”_ namespace, and then joins the tenant worker nodes to it.
A [Cluster API ControlPlane provider](https://github.com/clastix/cluster-api-control-plane-provider-kamaji) has been released, allowing to offer a Cluster API-native declarative lifecycle, by automating the worker nodes join.
## Datastores
Putting the Tenant Control Plane in a pod is the easiest part. Also, we have to make sure each tenant cluster saves the state to be able to store and retrieve data. As we can deploy a Kubernetes cluster with an external `etcd` cluster, we explored this option for the Tenant Control Planes. On the admin cluster, you can deploy one or multi-tenant `etcd` to save the state of multiple tenant clusters. Kamaji offers a Custom Resource Definition called `DataStore` to provide a declarative approach of managing multiple datastores. By sharing the datastore between multiple tenants, the resiliency is still guaranteed and the pods' count remains under control, so it solves the main goal of resiliency and costs optimization. The trade-off here is that you have to operate external datastores, in addition to `etcd` of the _“admin cluster”_ and manage the access to be sure that each _“tenant cluster”_ uses only its data.

View File

@@ -73,7 +73,7 @@ helm install \
## Install Kamaji Controller
Installing Kamaji via Helm charts is the preferred way. The Kamaji controller needs to access a Datastore in order to save data of the tenants' clusters. The Kamaji Helm Chart provides the installation of a basic unamanaged `etcd` as datastore, out of box.
Installing Kamaji via Helm charts is the preferred way. The Kamaji controller needs to access a Datastore in order to save data of the tenants' clusters. The Kamaji Helm Chart provides the installation of a basic unmanaged `etcd` as datastore, out of box.
Install Kamaji with `helm` using an unmanaged `etcd` as default datastore:
@@ -99,6 +99,8 @@ kind: TenantControlPlane
metadata:
name: ${TENANT_NAME}
namespace: ${TENANT_NAMESPACE}
labels:
tenant.clastix.io: ${TENANT_NAME}
spec:
dataStore: default
controlPlane:
@@ -240,10 +242,11 @@ And make sure it is `${TENANT_ADDR}:${TENANT_PORT}`.
### Prepare worker nodes to join
Currently Kamaji does not provide any helper for creation of tenant worker nodes. You should get a set of machines from your infrastructure provider, turn them into worker nodes, and then join to the tenant control plane with the `kubeadm`.
Currently, Kamaji does not provide any helper for creation of tenant worker nodes.
You should get a set of machines from your infrastructure provider, turn them into worker nodes, and then join to the tenant control plane with the `kubeadm`.
!!! note "Cluster APIs support"
In the future, we'll provide creation of tenant clusters through Cluster APIs.
Kamaji is sticking to the [Cluster Management API](https://github.com/kubernetes-sigs/cluster-api) project contracts by providing a `ControlPlane` provider.
Please, refer to the [official repository](https://github.com/clastix/cluster-api-control-plane-provider-kamaji) to learn more about it.
You can use the provided helper script `/deploy/nodes-prerequisites.sh`, in order to install the dependencies on all the worker nodes:

View File

@@ -0,0 +1,143 @@
# Certificates lifecycle
Kamaji is responsible for creating the required certificates, such as:
- the Kubernetes API Server certificate
- the Kubernetes API Server kubelet client certificate
- the Datastore certificate
- the front proxy client certificate
- the konnectivity certificate (if enabled)
Also, the following `kubeconfig` resources contain client certificates, which are created by Kamaji, such as:
- `admin`
- `controller-manager`
- `konnectivity` (if enabled)
- `scheduler`
All the certificates are created with the `kubeadm` defaults, thus their validity is set to 1 year.
## How to rotate certificates
If you need to manually rotate one of these certificates, the required operation is the deletion for the given Secret.
```
$: 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
```
Once this operation is performed, Kamaji will be notified of the missing certificate, and it will create it back.
```
$: 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 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
```
You can notice the secrets have been automatically created back, as well as a TenantControlPlane rollout with the updated certificates.
```
$: 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
```
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 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
```
## Automatic certificates rotation
The Kamaji operator will run a controller which processes all the Secrets to determine their expiration, both for the `kubeconfig`, as well as for the certificates.
The controller, named `CertificateLifecycle`, will extract the certificates from the _Secret_ objects notifying the `TenantControlPlaneReconciler` controller which will start a new certificate rotation.
The rotation will occur the day before their expiration.
> Nota Bene:
>
> Kamaji is responsible for creating the `etcd` client certificate, and the generation of a new one will occur.
> For other Datastore drivers, such as MySQL or PostgreSQL, the referenced Secret will always be deleted by the Controller to trigger the rotation:
> the PKI management, since it's offloaded externally, must provide the renewed certificates.
## Certificate Authority rotation
Kamaji is also taking care of your tenant clusters Certificate Authority.
This can be rotated manually by deleting the following secret.
```
$: kubectl delete secret k8s-126-ca
secret "k8s-126-ca" deleted
```
Once this occurs the TenantControlPlane will enter in the `CertificateAuthorityRotating` status.
```
$: 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
```
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.
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.

View File

@@ -114,7 +114,7 @@ helm install \
## Install Kamaji Controller
Installing Kamaji via Helm charts is the preferred way. The Kamaji controller needs to access a Datastore in order to save data of the tenants' clusters. The Kamaji Helm Chart provides the installation of a basic unamanaged `etcd` as datastore, out of box.
Installing Kamaji via Helm charts is the preferred way. The Kamaji controller needs to access a Datastore in order to save data of the tenants' clusters. The Kamaji Helm Chart provides the installation of a basic unmanaged `etcd` as datastore, out of box.
Install Kamaji with `helm` using an unmanaged `etcd` as default datastore:
@@ -144,6 +144,8 @@ kind: TenantControlPlane
metadata:
name: ${TENANT_NAME}
namespace: ${TENANT_NAMESPACE}
labels:
tenant.clastix.io: ${TENANT_NAME}
spec:
dataStore: default
controlPlane:
@@ -274,10 +276,11 @@ kubernetes 10.240.0.100:6443 57m
### Prepare worker nodes to join
Currently Kamaji does not provide any helper for creation of tenant worker nodes. You should get a set of machines from your infrastructure provider, turn them into worker nodes, and then join to the tenant control plane with the `kubeadm`.
Currently, Kamaji does not provide any helper for creation of tenant worker nodes.
You should get a set of machines from your infrastructure provider, turn them into worker nodes, and then join to the tenant control plane with the `kubeadm`.
!!! note "Cluster APIs support"
In the future, we'll provide creation of tenant clusters through Cluster APIs.
Kamaji is sticking to the [Cluster Management API](https://github.com/kubernetes-sigs/cluster-api) project contracts by providing a `ControlPlane` provider.
An Azure-based cluster is not yet available: the available road-map is available on the [official repository](https://github.com/clastix/cluster-api-control-plane-provider-kamaji).
Create an Azure VM Stateful Set to host worker nodes

View File

@@ -14,6 +14,8 @@ apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: tenant-00
labels:
tenant.clastix.io: tenant-00
spec:
controlPlane:
deployment:
@@ -27,6 +29,9 @@ spec:
```
## Upgrade of Tenant Worker Nodes
As currently Kamaji is not providing any helpers for Tenant Worker Nodes, you should make sure to upgrade them manually, for example, with the help of `kubeadm`. Refer to the official [documentation](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/#upgrade-worker-nodes).
> We have in roadmap, the Cluster APIs support so that you can upgrade _“tenant clusters”_ in a fully declarative way.
As currently Kamaji is not providing any helpers for Tenant Worker Nodes, you should make sure to upgrade them manually, for example, with the help of `kubeadm`.
Refer to the official [documentation](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/#upgrade-worker-nodes).
Kamaji is offering a [Cluster API Control Plane provider](https://github.com/clastix/cluster-api-control-plane-provider-kamaji), thus integrating with the Kubernetes clusters declarative management approach.
You can refer to the official [Cluster API documentation](https://cluster-api.sigs.k8s.io/).

View File

@@ -191,6 +191,8 @@ kind: TenantControlPlane
metadata:
name: benchmark$I
namespace: $NS
labels:
tenant.clastix.io: benchmark$I
spec:
dataStore: $DS
controlPlane:

View File

@@ -4,22 +4,24 @@ Currently, **Kamaji** allows customization using CLI flags for the `manager` sub
Available flags are the following:
| Flag | Usage | Default |
| ---- | ------ | --- |
| `--metrics-bind-address` | The address the metric endpoint binds to. | `:8080` |
| `--health-probe-bind-address` | The address the probe endpoint binds to. | `:8081` |
| `--leader-elect` | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. | `true` |
| `--tmp-directory` | Directory which will be used to work with temporary files. | `/tmp/kamaji` |
| `--kine-image` | Container image along with tag to use for the Kine sidecar container (used only if etcd-storage-type is set to one of kine strategies). | `rancher/kine:v0.9.2-amd64` |
| `--datastore` | The default DataStore that should be used by Kamaji to setup the required storage. | `etcd` |
| `--migrate-image` | Specify the container image to launch when a TenantControlPlane is migrated to a new datastore. | `migrate-image` |
| `--max-concurrent-tcp-reconciles` | Specify the number of workers for the Tenant Control Plane controller (beware of CPU consumption). | `1` |
| `--pod-namespace` | The Kubernetes Namespace on which the Operator is running in, required for the TenantControlPlane migration jobs. | `os.Getenv("POD_NAMESPACE")` |
| `--webhook-service-name` | The Kamaji webhook server Service name which is used to get validation webhooks, required for the TenantControlPlane migration jobs. | `kamaji-webhook-service` |
| `--serviceaccount-name` | The Kubernetes ServiceAccount used by the Operator, required for the TenantControlPlane migration jobs. | `os.Getenv("SERVICE_ACCOUNT")` |
| `--webhook-ca-path` | Path to the Manager webhook server CA, required for the TenantControlPlane migration jobs. | `/tmp/k8s-webhook-server/serving-certs/ca.crt` |
| `--zap-devel` | Development Mode (encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode (encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error). | `true` |
| `--zap-encoder` | Zap log encoding, one of 'json' or 'console' | `console` |
| `--zap-log-level` | Zap Level to configure the verbosity of logging. Can be one of 'debug', 'info', 'error', or any integer value > 0 which corresponds to custom debug levels of increasing verbosity | `info` |
| `--zap-stacktrace-level` | Zap Level at and above which stacktraces are captured (one of 'info', 'error', 'panic'). | `info` |
| `--zap-time-encoding` | Zap time encoding (one of 'epoch', 'millis', 'nano', 'iso8601', 'rfc3339' or 'rfc3339nano') | `epoch` |
| Flag | Usage | Default |
|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
| `--metrics-bind-address` | The address the metric endpoint binds to. | `:8080` |
| `--health-probe-bind-address` | The address the probe endpoint binds to. | `:8081` |
| `--leader-elect` | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. | `true` |
| `--tmp-directory` | Directory which will be used to work with temporary files. | `/tmp/kamaji` |
| `--kine-image` | Container image along with tag to use for the Kine sidecar container (used only if etcd-storage-type is set to one of kine strategies). | `rancher/kine:v0.9.2-amd64` |
| `--datastore` | The default DataStore that should be used by Kamaji to setup the required storage. | `etcd` |
| `--migrate-image` | Specify the container image to launch when a TenantControlPlane is migrated to a new datastore. | `migrate-image` |
| `--max-concurrent-tcp-reconciles` | Specify the number of workers for the Tenant Control Plane controller (beware of CPU consumption). | `1` |
| `--pod-namespace` | The Kubernetes Namespace on which the Operator is running in, required for the TenantControlPlane migration jobs. | `os.Getenv("POD_NAMESPACE")` |
| `--webhook-service-name` | The Kamaji webhook server Service name which is used to get validation webhooks, required for the TenantControlPlane migration jobs. | `kamaji-webhook-service` |
| `--serviceaccount-name` | The Kubernetes ServiceAccount used by the Operator, required for the TenantControlPlane migration jobs. | `os.Getenv("SERVICE_ACCOUNT")` |
| `--webhook-ca-path` | Path to the Manager webhook server CA, required for the TenantControlPlane migration jobs. | `/tmp/k8s-webhook-server/serving-certs/ca.crt` |
| `--controller-reconcile-timeout` | The reconciliation request timeout before the controller withdraw the external resource calls, such as dealing with the Datastore, or the Tenant Control Plane API endpoint. | `30s` |
| `--cache-resync-period` | The controller-runtime.Manager cache resync period. | `10h` |
| `--zap-devel` | Development Mode (encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode (encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error). | `true` |
| `--zap-encoder` | Zap log encoding, one of 'json' or 'console' | `console` |
| `--zap-log-level` | Zap Level to configure the verbosity of logging. Can be one of 'debug', 'info', 'error', or any integer value > 0 which corresponds to custom debug levels of increasing verbosity | `info` |
| `--zap-stacktrace-level` | Zap Level at and above which stacktraces are captured (one of 'info', 'error', 'panic'). | `info` |
| `--zap-time-encoding` | Zap time encoding (one of 'epoch', 'millis', 'nano', 'iso8601', 'rfc3339' or 'rfc3339nano') | `epoch` |

View File

@@ -2,9 +2,12 @@
In Kamaji, there are different components that might require independent versioning and support level:
|Kamaji |Admin Cluster| Tenant Cluster |
|-------|-------------|----------------------|
| v0.0 | v1.22+ | [v1.21.0 .. v1.23.5] |
| v0.1 | v1.22+ | [v1.21.0 .. v1.25.0] |
| v0.2 | v1.22+ | [v1.21.0 .. v1.27.0] |
| Kamaji | Admin Cluster | Tenant Cluster |
|--------|---------------|----------------------|
| v0.0 | v1.22+ | [v1.21.0 .. v1.23.5] |
| v0.1 | v1.22+ | [v1.21.0 .. v1.25.0] |
| v0.2 | v1.22+ | [v1.21.0 .. v1.27.0] |
| v0.3.0 | v1.22+ | [v1.21.0 .. v1.27.0] |
| v0.3.1 | v1.22+ | [v1.21.0 .. v1.27.3] |
| v0.3.2 | v1.22+ | [v1.21.0 .. v1.27.3] |
| v0.3.2 | v1.22+ | [v1.21.0 .. v1.27.3] |

View File

@@ -65,6 +65,7 @@ nav:
- guides/upgrade.md
- guides/datastore-migration.md
- guides/backup-and-restore.md
- guides/certs-lifecycle.md
- 'Use Cases': use-cases.md
- 'Reference':
- reference/index.md

View File

@@ -9,4 +9,5 @@ const (
ControlPlaneLabelKey = "kamaji.clastix.io/name"
ControlPlaneLabelResource = "kamaji.clastix.io/component"
ControllerLabelResource = "kamaji.clastix.io/certificate_lifecycle_controller"
)

View File

@@ -194,9 +194,11 @@ func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Ce
}
func checkCertificateValidity(cert x509.Certificate) bool {
now := time.Now()
// Avoiding waiting for the exact expiration date by creating a one-day gap
notAfter := cert.NotAfter.After(time.Now().AddDate(0, 0, 1))
notBefore := cert.NotBefore.Before(time.Now())
return now.Before(cert.NotAfter) && now.After(cert.NotBefore)
return notAfter && notBefore
}
func checkPublicKeys(a rsa.PublicKey, b rsa.PublicKey) bool {

View File

@@ -35,7 +35,6 @@ func CreateKubeadmInitConfiguration(params Parameters) (*Configuration, error) {
AdvertiseAddress: params.TenantControlPlaneAddress,
BindPort: params.TenantControlPlanePort,
}
conf.NodeRegistration.Name = params.TenantControlPlaneName
caFile, certFile, keyFile := "", "", ""
if strings.HasPrefix(params.ETCDs[0], "https") {

View File

@@ -10,6 +10,9 @@ import (
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/utilities"
)
func buildCertificateDirectoryWithCA(ca CertificatePrivateKeyPair, directory string) error {
@@ -46,6 +49,13 @@ func CreateKubeconfig(kubeconfigName string, ca CertificatePrivateKeyPair, confi
return os.ReadFile(path)
}
func IsKubeconfigValid(kubeconfigBytes []byte) bool {
return len(kubeconfigBytes) > 0
func IsKubeconfigValid(bytes []byte) bool {
kc, err := utilities.DecodeKubeconfigYAML(bytes)
if err != nil {
return false
}
ok, _ := crypto.IsValidCertificateKeyPairBytes(kc.AuthInfos[0].AuthInfo.ClientCertificateData, kc.AuthInfos[0].AuthInfo.ClientKeyData)
return ok
}

View File

@@ -4,6 +4,9 @@
package kubeadm
import (
"fmt"
"github.com/blang/semver"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -19,6 +22,14 @@ import (
"github.com/clastix/kamaji/internal/utilities"
)
const (
// kubeletConfigMapName defines base kubelet configuration ConfigMap name for kubeadm < 1.24.
kubeletConfigMapName = "kubelet-config-%d.%d"
)
// minVerUnversionedKubeletConfig defines minimum version from which kubeadm uses kubelet-config as a ConfigMap name.
var minVerUnversionedKubeletConfig = semver.MustParse("1.24.0")
func UploadKubeadmConfig(client kubernetes.Interface, config *Configuration) ([]byte, error) {
return nil, uploadconfig.UploadConfiguration(&config.InitConfiguration, client)
}
@@ -34,7 +45,10 @@ func UploadKubeletConfig(client kubernetes.Interface, config *Configuration) ([]
return nil, err
}
configMapName := kubeadmconstants.KubeletBaseConfigurationConfigMap
configMapName, err := generateKubeletConfigMapName(config.Parameters.TenantControlPlaneVersion)
if err != nil {
return nil, err
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
@@ -50,7 +64,7 @@ func UploadKubeletConfig(client kubernetes.Interface, config *Configuration) ([]
return nil, err
}
if err = createConfigMapRBACRules(client); err != nil {
if err = createConfigMapRBACRules(client, configMapName); err != nil {
return nil, errors.Wrap(err, "error creating kubelet configuration configmap RBAC rules")
}
@@ -114,7 +128,7 @@ func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration) ([]by
return utilities.EncodeToYaml(&kc)
}
func createConfigMapRBACRules(client kubernetes.Interface) error {
func createConfigMapRBACRules(client kubernetes.Interface, configMapName string) error {
configMapRBACName := kubeadmconstants.KubeletBaseConfigMapRole
if err := apiclient.CreateOrUpdateRole(client, &rbacv1.Role{
@@ -127,7 +141,7 @@ func createConfigMapRBACRules(client kubernetes.Interface) error {
Verbs: []string{"get"},
APIGroups: []string{""},
Resources: []string{"configmaps"},
ResourceNames: []string{kubeadmconstants.KubeletBaseConfigurationConfigMap},
ResourceNames: []string{configMapName},
},
},
}); err != nil {
@@ -156,3 +170,17 @@ func createConfigMapRBACRules(client kubernetes.Interface) error {
},
})
}
func generateKubeletConfigMapName(version string) (string, error) {
parsedVersion, err := semver.ParseTolerant(version)
if err != nil {
return "", errors.Wrapf(err, "failed to parse kubernetes version %q", version)
}
majorMinor := semver.Version{Major: parsedVersion.Major, Minor: parsedVersion.Minor}
if majorMinor.GTE(minVerUnversionedKubeletConfig) {
return kubeadmconstants.KubeletBaseConfigurationConfigMap, nil
}
return fmt.Sprintf(kubeletConfigMapName, parsedVersion.Major, parsedVersion.Minor), nil
}

View File

@@ -18,6 +18,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/kubeadm"
"github.com/clastix/kamaji/internal/utilities"
@@ -93,6 +94,19 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
return err
}
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
},
))
if err := ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
logger.Error(err, "cannot set controller reference", "resource", r.GetName())
return err
}
if checksum := tenantControlPlane.Status.Certificates.APIServer.Checksum; 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 {
@@ -138,8 +152,6 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
utilities.SetObjectChecksum(r.resource, r.resource.Data)
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
return nil
}
}

View File

@@ -18,6 +18,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/kubeadm"
"github.com/clastix/kamaji/internal/utilities"
@@ -93,6 +94,19 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
return err
}
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
},
))
if err := ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
logger.Error(err, "cannot set controller reference", "resource", r.GetName())
return err
}
if checksum := tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum; 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 {
@@ -136,10 +150,8 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
kubeadmconstants.APIServerKubeletClientKeyName: certificateKeyPair.PrivateKey,
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
utilities.SetObjectChecksum(r.resource, r.resource.Data)
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
return nil
}
}

View File

@@ -4,6 +4,7 @@
package resources
import (
"bytes"
"context"
"fmt"
@@ -96,6 +97,13 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
if err != nil {
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.CACertAndKeyBaseName, err.Error()))
}
// Appending the Cluster API required keys if they're missing:
// with this we're sure to avoid introducing breaking changes.
if isValid && (!bytes.Equal(r.resource.Data[corev1.TLSCertKey], r.resource.Data[kubeadmconstants.CACertName]) || !bytes.Equal(r.resource.Data[kubeadmconstants.CAKeyName], r.resource.Data[corev1.TLSPrivateKeyKey])) {
r.resource.Data[corev1.TLSCertKey] = r.resource.Data[kubeadmconstants.CACertName]
r.resource.Data[corev1.TLSPrivateKeyKey] = r.resource.Data[kubeadmconstants.CAKeyName]
}
if isValid {
return nil
}
@@ -122,6 +130,11 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
r.resource.Data = map[string][]byte{
kubeadmconstants.CACertName: ca.Certificate,
kubeadmconstants.CAKeyName: ca.PrivateKey,
// Required for Cluster API integration which is reading the basic TLS keys.
// We cannot switch over basic corev1.Secret keys for backward compatibility,
// it would require a new CA generation breaking all the clusters deployed.
corev1.TLSCertKey: ca.Certificate,
corev1.TLSPrivateKeyKey: ca.PrivateKey,
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))

View File

@@ -16,6 +16,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/utilities"
)
@@ -88,6 +89,19 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
r.resource.Data["ca.crt"] = ca
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
},
))
if err = ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
logger.Error(err, "cannot set controller reference", "resource", r.GetName())
return err
}
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 {
@@ -141,11 +155,6 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
utilities.SetObjectChecksum(r.resource, r.resource.Data)
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
r.resource.GetLabels(),
))
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
return nil
}
}

View File

@@ -6,6 +6,7 @@ package datastore
import (
"context"
"fmt"
"strings"
"github.com/google/uuid"
corev1 "k8s.io/api/core/v1"
@@ -94,8 +95,9 @@ func (r *Config) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.Te
if len(fromStatus) > 0 {
return []byte(fromStatus)
}
return []byte(fmt.Sprintf("%s_%s", tenantControlPlane.GetNamespace(), tenantControlPlane.GetName()))
// 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()), "-", "_"))
}
r.resource.Data = map[string][]byte{

View File

@@ -18,6 +18,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/kubeadm"
"github.com/clastix/kamaji/internal/utilities"
@@ -92,6 +93,20 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
return err
}
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
},
))
if err := ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
logger.Error(err, "cannot set controller reference", "resource", r.GetName())
return err
}
if checksum := tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum; 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 {
@@ -135,10 +150,8 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
kubeadmconstants.FrontProxyClientKeyName: certificateKeyPair.PrivateKey,
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
utilities.SetObjectChecksum(r.resource, r.resource.Data)
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
return nil
}
}

View File

@@ -147,7 +147,7 @@ func (r *KubernetesIngressResource) mutate(tenantControlPlane *kamajiv1alpha1.Te
return fmt.Errorf("missing hostname to expose the Tenant Control Plane using an Ingress resource")
}
rule.Host = tenantControlPlane.Spec.ControlPlane.Ingress.Hostname
rule.Host, _ = utilities.GetControlPlaneAddressAndPortFromHostname(tenantControlPlane.Spec.ControlPlane.Ingress.Hostname, 0)
r.resource.Spec.Rules = []networkingv1.IngressRule{
rule,

View File

@@ -18,6 +18,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/kubeadm"
"github.com/clastix/kamaji/internal/utilities"
@@ -89,6 +90,19 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
return func() error {
logger := log.FromContext(ctx, "resource", r.GetName())
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "x509",
},
))
if err := ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
logger.Error(err, "cannot set controller reference", "resource", r.GetName())
return err
}
if checksum := tenantControlPlane.Status.Addons.Konnectivity.Certificate.Checksum; 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 {
@@ -126,10 +140,8 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
corev1.TLSPrivateKeyKey: privKey.Bytes(),
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
utilities.SetObjectChecksum(r.resource, r.resource.Data)
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
return nil
}
}

View File

@@ -19,6 +19,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/utilities"
)
@@ -87,6 +88,19 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
return func() error {
logger := log.FromContext(ctx, "resource", r.GetName())
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "kubeconfig",
},
))
if err := ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
logger.Error(err, "cannot set controller reference for kubeconfig", "resource", r.GetName())
return err
}
if checksum := tenantControlPlane.Status.Addons.Konnectivity.Certificate.Checksum; len(checksum) > 0 && checksum == utilities.GetObjectChecksum(r.resource) {
return nil
}
@@ -157,8 +171,6 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
utilities.SetObjectChecksum(r.resource, r.resource.Data)
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
return nil
}
}

View File

@@ -142,6 +142,9 @@ func (r *ServiceResource) mutate(_ context.Context, tenantControlPlane *kamajiv1
r.resource.Spec.Ports[1].Protocol = corev1.ProtocolTCP
r.resource.Spec.Ports[1].Port = tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityServerSpec.Port
r.resource.Spec.Ports[1].TargetPort = intstr.FromInt(int(tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityServerSpec.Port))
if tenantControlPlane.Spec.ControlPlane.Service.ServiceType == kamajiv1alpha1.ServiceTypeNodePort {
r.resource.Spec.Ports[1].NodePort = tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityServerSpec.Port
}
return controllerutil.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
}

View File

@@ -71,7 +71,7 @@ func (r *KubeadmConfigResource) UpdateTenantControlPlaneStatus(ctx context.Conte
func (r *KubeadmConfigResource) getControlPlaneEndpoint(ingress *kamajiv1alpha1.IngressSpec, address string, port int32) string {
if ingress != nil && len(ingress.Hostname) > 0 {
return ingress.Hostname
address, port = utilities.GetControlPlaneAddressAndPortFromHostname(ingress.Hostname, port)
}
return fmt.Sprintf("%s:%d", address, port)

View File

@@ -117,10 +117,10 @@ func (r *KubeconfigResource) CreateOrUpdate(ctx context.Context, tenantControlPl
return utilities.CreateOrUpdateWithConflict(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
}
func (r *KubeconfigResource) checksum(apiServerCertificatesSecret *corev1.Secret, kubeadmChecksum string) string {
func (r *KubeconfigResource) checksum(caCertificatesSecret *corev1.Secret, kubeadmChecksum string) string {
return utilities.CalculateMapChecksum(map[string][]byte{
"ca-cert-checksum": apiServerCertificatesSecret.Data[kubeadmconstants.CACertName],
"ca-key-checksum": apiServerCertificatesSecret.Data[kubeadmconstants.CAKeyName],
"ca-cert-checksum": caCertificatesSecret.Data[kubeadmconstants.CACertName],
"ca-key-checksum": caCertificatesSecret.Data[kubeadmconstants.CAKeyName],
"kubeadmconfig": []byte(kubeadmChecksum),
})
}
@@ -142,15 +142,15 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
return err
}
apiServerCertificatesSecretNamespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
apiServerCertificatesSecret := &corev1.Secret{}
if err := r.Client.Get(ctx, apiServerCertificatesSecretNamespacedName, apiServerCertificatesSecret); err != nil {
caSecretNamespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
caCertificatesSecret := &corev1.Secret{}
if err = r.Client.Get(ctx, caSecretNamespacedName, caCertificatesSecret); err != nil {
logger.Error(err, "cannot retrieve the CA")
return err
}
checksum := r.checksum(apiServerCertificatesSecret, config.Checksum())
checksum := r.checksum(caCertificatesSecret, config.Checksum())
status, err := r.getKubeconfigStatus(tenantControlPlane)
if err != nil {
@@ -158,38 +158,48 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
return err
}
// A new kubeconfig must be generated when one of the following cases is occurring:
// 1. the status checksum is different from the computed one
// 2. the resource UID is empty, meaning it's a new resource (tl;dr; a first reconciliation)
//
// And finally, we're checking if the kubeconfig is valid: if not, generating a new one.
if (status.Checksum != checksum || len(r.resource.UID) == 0) && !kubeadm.IsKubeconfigValid(r.resource.Data[r.KubeConfigFileName]) {
kubeconfig, err := kubeadm.CreateKubeconfig(
r.KubeConfigFileName,
kubeadm.CertificatePrivateKeyPair{
Certificate: apiServerCertificatesSecret.Data[kubeadmconstants.CACertName],
PrivateKey: apiServerCertificatesSecret.Data[kubeadmconstants.CAKeyName],
},
config,
)
if err != nil {
logger.Error(err, "cannot create a valid kubeconfig")
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
map[string]string{
constants.ControllerLabelResource: "kubeconfig",
},
))
r.resource.SetAnnotations(map[string]string{constants.Checksum: checksum})
return err
if err = ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
logger.Error(err, "cannot set controller reference", "resource", r.GetName())
return err
}
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 || !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
if shouldCreate {
crtKeyPair := kubeadm.CertificatePrivateKeyPair{
Certificate: caCertificatesSecret.Data[kubeadmconstants.CACertName],
PrivateKey: caCertificatesSecret.Data[kubeadmconstants.CAKeyName],
}
kubeconfig, kcErr := kubeadm.CreateKubeconfig(r.KubeConfigFileName, crtKeyPair, config)
if kcErr != nil {
logger.Error(kcErr, "cannot create a valid kubeconfig")
return kcErr
}
r.resource.Data = map[string][]byte{
r.KubeConfigFileName: kubeconfig,
}
}
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
r.resource.SetAnnotations(map[string]string{
constants.Checksum: checksum,
})
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
return nil
}
}

View File

@@ -4,5 +4,5 @@
package upgrade
const (
KubeadmVersion = "v1.27.0"
KubeadmVersion = "v1.27.3"
)

View File

@@ -0,0 +1,25 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package utilities
import (
"strconv"
"strings"
)
func GetControlPlaneAddressAndPortFromHostname(hostname string, defaultPort int32) (address string, port int32) {
parts := strings.Split(hostname, ":")
address, port = parts[0], defaultPort
if len(parts) == 2 {
intPort, _ := strconv.Atoi(parts[1])
if intPort > 0 {
port = int32(intPort)
}
}
return address, port
}

View File

@@ -0,0 +1,29 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package utilities
import (
"fmt"
corev1 "k8s.io/api/core/v1"
clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1"
)
func DecodeKubeconfig(secret corev1.Secret, key string) (*clientcmdapiv1.Config, error) {
bytes, ok := secret.Data[key]
if !ok {
return nil, fmt.Errorf("%s is not into kubeconfig secret", key)
}
return DecodeKubeconfigYAML(bytes)
}
func DecodeKubeconfigYAML(bytes []byte) (*clientcmdapiv1.Config, error) {
kubeconfig := &clientcmdapiv1.Config{}
if err := DecodeFromYAML(string(bytes), kubeconfig); err != nil {
return nil, err
}
return kubeconfig, nil
}

View File

@@ -44,17 +44,7 @@ func GetTenantKubeconfig(ctx context.Context, client client.Client, tenantContro
return nil, err
}
bytes, ok := secretKubeconfig.Data[kubeadmconstants.AdminKubeConfigFileName]
if !ok {
return nil, fmt.Errorf("%s is not into kubeconfig secret", kubeadmconstants.AdminKubeConfigFileName)
}
kubeconfig := &clientcmdapiv1.Config{}
if err := DecodeFromYAML(string(bytes), kubeconfig); err != nil {
return nil, err
}
return kubeconfig, nil
return DecodeKubeconfig(*secretKubeconfig, kubeadmconstants.AdminKubeConfigFileName)
}
func GetRESTClientConfig(ctx context.Context, client client.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*restclient.Config, error) {

View File

@@ -0,0 +1,40 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"context"
"fmt"
"strings"
"gomodules.xyz/jsonpatch/v2"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/webhook/utils"
)
type TenantControlPlaneName struct{}
func (t TenantControlPlaneName) OnCreate(object runtime.Object) AdmissionResponse {
return func(ctx context.Context, req admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
if errs := validation.IsDNS1035Label(tcp.Name); len(errs) > 0 {
return nil, fmt.Errorf("the provided name is invalid, %s", strings.Join(errs, ","))
}
return nil, nil
}
}
func (t TenantControlPlaneName) OnDelete(runtime.Object) AdmissionResponse {
return utils.NilOp()
}
func (t TenantControlPlaneName) OnUpdate(runtime.Object, runtime.Object) AdmissionResponse {
return utils.NilOp()
}

View File

@@ -4,9 +4,8 @@
package routes
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
type TenantControlPlaneMigrate struct{}
@@ -16,5 +15,5 @@ func (t TenantControlPlaneMigrate) GetPath() string {
}
func (t TenantControlPlaneMigrate) GetObject() runtime.Object {
return &kamajiv1alpha1.TenantControlPlane{}
return &corev1.Namespace{}
}