Files
kubesphere/pkg/controller/ksserviceaccount/serviceaccount_controller.go
smartcat999 9fab44d0bf fix: update console permission with ks service account (#2135)
* fix: update console permission with ks service account

Signed-off-by: peng wu <2030047311@qq.com>

* fix: update restart pod's annotation

Signed-off-by: peng wu <2030047311@qq.com>

* fix: update restart pod condition

Signed-off-by: peng wu <2030047311@qq.com>

* fix: service account pod restart condition

Signed-off-by: peng wu <2030047311@qq.com>

* fix: service account pod restart condition

Signed-off-by: peng wu <2030047311@qq.com>

* fix: update goimports

Signed-off-by: peng wu <2030047311@qq.com>

---------

Signed-off-by: peng wu <2030047311@qq.com>
Signed-off-by: hongming <coder.scala@gmail.com>
2025-03-19 06:26:25 +00:00

291 lines
8.8 KiB
Go

/*
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package ksserviceaccount
import (
"context"
"fmt"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
corev1alpha1 "kubesphere.io/api/core/v1alpha1"
kscontroller "kubesphere.io/kubesphere/pkg/controller"
)
const (
controllerName = "ks-serviceaccount"
finalizer = "finalizers.kubesphere.io/serviceaccount"
messageCreateSecretSuccessfully = "Create token secret successfully"
reasonInvalidSecret = "InvalidSecret"
)
var _ kscontroller.Controller = &Reconciler{}
func (r *Reconciler) Name() string {
return controllerName
}
type Reconciler struct {
client.Client
Scheme *runtime.Scheme
Logger logr.Logger
EventRecorder record.EventRecorder
}
func (r *Reconciler) SetupWithManager(mgr *kscontroller.Manager) error {
r.Client = mgr.GetClient()
r.EventRecorder = mgr.GetEventRecorderFor(controllerName)
r.Logger = ctrl.Log.WithName("controllers").WithName(controllerName)
return builder.
ControllerManagedBy(mgr).
For(
&corev1alpha1.ServiceAccount{},
builder.WithPredicates(
predicate.ResourceVersionChangedPredicate{},
),
).
Named(controllerName).
WithOptions(controller.Options{
MaxConcurrentReconciles: 2,
}).
Complete(r)
}
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (reconcile.Result, error) {
logger := r.Logger.WithValues(req.NamespacedName, "ServiceAccount")
sa := &corev1alpha1.ServiceAccount{}
if err := r.Get(ctx, req.NamespacedName, sa); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if sa.ObjectMeta.DeletionTimestamp.IsZero() {
if !controllerutil.ContainsFinalizer(sa, finalizer) {
deepCopy := sa.DeepCopy()
deepCopy.Finalizers = append(deepCopy.Finalizers, finalizer)
if len(sa.Secrets) == 0 {
secretCreated, err := r.createTokenSecret(ctx, sa)
if err != nil {
logger.Error(err, "create secret failed")
return ctrl.Result{}, err
}
logger.V(4).WithName(secretCreated.Name).Info("secret created successfully")
deepCopy.Secrets = append(deepCopy.Secrets, v1.ObjectReference{
Namespace: secretCreated.Namespace,
Name: secretCreated.Name,
})
r.EventRecorder.Event(deepCopy, corev1.EventTypeNormal, kscontroller.Synced, messageCreateSecretSuccessfully)
}
if err := r.Update(ctx, deepCopy); err != nil {
logger.Error(err, "update serviceaccount failed")
return ctrl.Result{}, err
}
}
} else {
if controllerutil.ContainsFinalizer(sa, finalizer) {
if err := r.deleteSecretToken(ctx, sa, logger); err != nil {
logger.Error(err, "delete secret failed")
return ctrl.Result{}, err
}
_ = controllerutil.RemoveFinalizer(sa, finalizer)
if err := r.Update(ctx, sa); err != nil {
logger.Error(err, "update serviceaccount failed")
return ctrl.Result{}, err
}
}
}
if err := r.checkAllSecret(ctx, sa); err != nil {
logger.Error(err, "failed check secrets")
return ctrl.Result{}, err
}
if err := r.checkServiceAccountRefPod(ctx, sa); err != nil {
logger.Error(err, "failed check service account ref pod")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *Reconciler) createTokenSecret(ctx context.Context, sa *corev1alpha1.ServiceAccount) (*v1.Secret, error) {
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-", sa.Name),
Namespace: sa.Namespace,
Annotations: map[string]string{corev1alpha1.ServiceAccountName: sa.Name},
},
Type: corev1alpha1.SecretTypeServiceAccountToken,
}
return secret, r.Client.Create(ctx, secret)
}
func (r *Reconciler) deleteSecretToken(ctx context.Context, sa *corev1alpha1.ServiceAccount, logger logr.Logger) error {
for _, secretName := range sa.Secrets {
secret := &v1.Secret{}
if err := r.Get(ctx, client.ObjectKey{Namespace: secretName.Namespace, Name: secretName.Name}, secret); err != nil {
if errors.IsNotFound(err) {
continue
} else {
return err
}
}
if err := r.checkSecretToken(secret, sa.Name); err == nil {
if err = r.Delete(ctx, secret); err != nil {
return err
}
logger.V(2).WithName(secretName.Name).Info("delete secret successfully")
}
}
return nil
}
func (r *Reconciler) checkAllSecret(ctx context.Context, sa *corev1alpha1.ServiceAccount) error {
for _, secretRef := range sa.Secrets {
secret := &v1.Secret{}
if err := r.Get(ctx, client.ObjectKey{Namespace: sa.Namespace, Name: secretRef.Name}, secret); err != nil {
if errors.IsNotFound(err) {
r.EventRecorder.Event(sa, corev1.EventTypeWarning, reasonInvalidSecret, err.Error())
continue
}
return err
}
if err := r.checkSecretToken(secret, sa.Name); err != nil {
r.EventRecorder.Event(sa, corev1.EventTypeWarning, reasonInvalidSecret, err.Error())
}
}
return nil
}
// checkSecretTokens Check if there has valid token, and the invalid token reference will be deleted
func (r *Reconciler) checkSecretToken(secret *v1.Secret, subjectName string) error {
if secret.Type != corev1alpha1.SecretTypeServiceAccountToken {
return fmt.Errorf("unsupported secret %s type: %s", secret.Name, secret.Type)
}
if saName := secret.Annotations[corev1alpha1.ServiceAccountName]; saName != subjectName {
return fmt.Errorf("incorrect subject name %s", saName)
}
return nil
}
func (r *Reconciler) checkServiceAccountRefPod(ctx context.Context, sa *corev1alpha1.ServiceAccount) error {
if len(sa.Secrets) == 0 {
klog.Warningf("service account %s has no secrets", sa.Name)
return nil
}
pods := &v1.PodList{}
if err := r.Client.List(ctx, pods, client.InNamespace(sa.Namespace)); err != nil {
return err
}
saSecrets := sa.Secrets[0].Name
for _, pod := range pods.Items {
if pod.Annotations[AnnotationServiceAccountName] != sa.Name {
continue
}
for _, volume := range pod.Spec.Volumes {
if volume.Name == ServiceAccountVolumeName &&
len(volume.Projected.Sources) > 0 &&
saSecrets == volume.Projected.Sources[0].Secret.Name {
continue
}
}
if err := r.rolloutRestartPod(ctx, &pod); err != nil {
return nil
}
}
return nil
}
func (r *Reconciler) rolloutRestartPod(ctx context.Context, pod *v1.Pod) error {
// check ownerReferences
if len(pod.OwnerReferences) == 0 {
klog.Infof("Pod has no owner references")
return nil
}
owner := pod.OwnerReferences[0]
switch owner.Kind {
case "ReplicaSet":
rs := &appsv1.ReplicaSet{}
if err := r.Client.Get(ctx, types.NamespacedName{
Namespace: pod.Namespace,
Name: owner.Name,
}, rs); err != nil {
return err
}
if len(rs.OwnerReferences) > 0 && rs.OwnerReferences[0].Kind == "Deployment" {
deployName := rs.OwnerReferences[0].Name
deploy := &appsv1.Deployment{}
if err := r.Client.Get(ctx, types.NamespacedName{
Namespace: pod.Namespace,
Name: deployName,
}, deploy); err != nil {
return err
}
if deploy.Spec.Template.ObjectMeta.Annotations == nil {
deploy.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
deploy.Spec.Template.ObjectMeta.Annotations["kubesphere.io/restartedAt"] = metav1.Now().String()
if err := r.Client.Update(ctx, deploy); err != nil {
return err
}
}
case "StatefulSet":
sts := &appsv1.StatefulSet{}
if err := r.Client.Get(ctx, types.NamespacedName{
Namespace: pod.Namespace,
Name: owner.Name,
}, sts); err != nil {
return err
}
if sts.Spec.Template.ObjectMeta.Annotations == nil {
sts.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
sts.Spec.Template.ObjectMeta.Annotations["kubesphere.io/restartedAt"] = metav1.Now().String()
if err := r.Client.Update(ctx, sts); err != nil {
return err
}
case "DaemonSet":
ds := &appsv1.DaemonSet{}
if err := r.Client.Get(ctx, types.NamespacedName{
Namespace: pod.Namespace,
Name: owner.Name,
}, ds); err != nil {
return err
}
if ds.Spec.Template.ObjectMeta.Annotations == nil {
ds.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
}
ds.Spec.Template.ObjectMeta.Annotations["kubesphere.io/restartedAt"] = metav1.Now().String()
if err := r.Client.Update(ctx, ds); err != nil {
return err
}
default:
klog.Warningf("Unsupported owner kind %s", owner.Kind)
}
return nil
}