@@ -0,0 +1,292 @@
|
||||
/*
|
||||
Copyright 2019 The KubeSphere authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package certificatesigningrequest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/informers"
|
||||
certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
certificateslisters "k8s.io/client-go/listers/certificates/v1beta1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models/kubeconfig"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// SuccessSynced is used as part of the Event 'reason' when a Foo is csrSynced
|
||||
successSynced = "Synced"
|
||||
// is csrSynced successfully
|
||||
messageResourceSynced = "CertificateSigningRequest csrSynced successfully"
|
||||
controllerName = "csr-controller"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
k8sclient kubernetes.Interface
|
||||
csrInformer certificatesinformers.CertificateSigningRequestInformer
|
||||
csrLister certificateslisters.CertificateSigningRequestLister
|
||||
csrSynced cache.InformerSynced
|
||||
|
||||
cmInformer coreinformers.ConfigMapInformer
|
||||
cmLister corelisters.ConfigMapLister
|
||||
cmSynced cache.InformerSynced
|
||||
|
||||
// workqueue is a rate limited work queue. This is used to queue work to be
|
||||
// processed instead of performing it as soon as a change happens. This
|
||||
// means we can ensure we only process a fixed amount of resources at a
|
||||
// time, and makes it easy to ensure we are never processing the same item
|
||||
// simultaneously in two different workers.
|
||||
workqueue workqueue.RateLimitingInterface
|
||||
// recorder is an event recorder for recording Event resources to the
|
||||
// Kubernetes API.
|
||||
recorder record.EventRecorder
|
||||
kubeconfigOperator kubeconfig.Interface
|
||||
}
|
||||
|
||||
func NewController(k8sClient kubernetes.Interface, informerFactory informers.SharedInformerFactory, config *rest.Config) *Controller {
|
||||
// Create event broadcaster
|
||||
// Add sample-controller types to the default Kubernetes Scheme so Events can be
|
||||
// logged for sample-controller types.
|
||||
|
||||
klog.V(4).Info("Creating event broadcaster")
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(klog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName})
|
||||
csrInformer := informerFactory.Certificates().V1beta1().CertificateSigningRequests()
|
||||
cmInformer := informerFactory.Core().V1().ConfigMaps()
|
||||
ctl := &Controller{
|
||||
k8sclient: k8sClient,
|
||||
csrInformer: csrInformer,
|
||||
csrLister: csrInformer.Lister(),
|
||||
csrSynced: csrInformer.Informer().HasSynced,
|
||||
cmInformer: cmInformer,
|
||||
cmLister: cmInformer.Lister(),
|
||||
cmSynced: cmInformer.Informer().HasSynced,
|
||||
kubeconfigOperator: kubeconfig.NewOperator(k8sClient, config, ""),
|
||||
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "CertificateSigningRequest"),
|
||||
recorder: recorder,
|
||||
}
|
||||
klog.Info("Setting up event handlers")
|
||||
csrInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: ctl.enqueueCertificateSigningRequest,
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
ctl.enqueueCertificateSigningRequest(new)
|
||||
},
|
||||
DeleteFunc: ctl.enqueueCertificateSigningRequest,
|
||||
})
|
||||
return ctl
|
||||
}
|
||||
|
||||
func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.workqueue.ShutDown()
|
||||
|
||||
//init client
|
||||
|
||||
// Start the csrInformer factories to begin populating the csrInformer caches
|
||||
klog.Info("Starting User controller")
|
||||
|
||||
// Wait for the caches to be csrSynced before starting workers
|
||||
klog.Info("Waiting for csrInformer caches to sync")
|
||||
if ok := cache.WaitForCacheSync(stopCh, c.csrSynced, c.cmSynced); !ok {
|
||||
return fmt.Errorf("failed to wait for caches to sync")
|
||||
}
|
||||
|
||||
klog.Info("Starting workers")
|
||||
// Launch two workers to process Foo resources
|
||||
for i := 0; i < threadiness; i++ {
|
||||
go wait.Until(c.runWorker, time.Second, stopCh)
|
||||
}
|
||||
|
||||
klog.Info("Started workers")
|
||||
<-stopCh
|
||||
klog.Info("Shutting down workers")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) enqueueCertificateSigningRequest(obj interface{}) {
|
||||
var key string
|
||||
var err error
|
||||
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
c.workqueue.Add(key)
|
||||
}
|
||||
|
||||
func (c *Controller) runWorker() {
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) processNextWorkItem() bool {
|
||||
obj, shutdown := c.workqueue.Get()
|
||||
|
||||
if shutdown {
|
||||
return false
|
||||
}
|
||||
|
||||
// We wrap this block in a func so we can defer c.workqueue.Done.
|
||||
err := func(obj interface{}) error {
|
||||
// We call Done here so the workqueue knows we have finished
|
||||
// processing this item. We also must remember to call Forget if we
|
||||
// do not want this work item being re-queued. For example, we do
|
||||
// not call Forget if a transient error occurs, instead the item is
|
||||
// put back on the workqueue and attempted again after a back-off
|
||||
// period.
|
||||
defer c.workqueue.Done(obj)
|
||||
var key string
|
||||
var ok bool
|
||||
// We expect strings to come off the workqueue. These are of the
|
||||
// form namespace/name. We do this as the delayed nature of the
|
||||
// workqueue means the items in the csrInformer cache may actually be
|
||||
// more up to date that when the item was initially put onto the
|
||||
// workqueue.
|
||||
if key, ok = obj.(string); !ok {
|
||||
// As the item in the workqueue is actually invalid, we call
|
||||
// Forget here else we'd go into a loop of attempting to
|
||||
// process a work item that is invalid.
|
||||
c.workqueue.Forget(obj)
|
||||
utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
|
||||
return nil
|
||||
}
|
||||
// Run the reconcile, passing it the namespace/name string of the
|
||||
// Foo resource to be csrSynced.
|
||||
if err := c.reconcile(key); err != nil {
|
||||
// Put the item back on the workqueue to handle any transient errors.
|
||||
c.workqueue.AddRateLimited(key)
|
||||
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
|
||||
}
|
||||
// Finally, if no error occurs we Forget this item so it does not
|
||||
// get queued again until another change happens.
|
||||
c.workqueue.Forget(obj)
|
||||
klog.Infof("Successfully csrSynced %s:%s", "key", key)
|
||||
return nil
|
||||
}(obj)
|
||||
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// syncHandler compares the actual state with the desired, and attempts to
|
||||
// converge the two. It then updates the Status block of the Foo resource
|
||||
// with the current status of the resource.
|
||||
func (c *Controller) reconcile(key string) error {
|
||||
|
||||
// Get the CertificateSigningRequest with this name
|
||||
csr, err := c.csrLister.Get(key)
|
||||
if err != nil {
|
||||
// The user may no longer exist, in which case we stop
|
||||
// processing.
|
||||
if errors.IsNotFound(err) {
|
||||
utilruntime.HandleError(fmt.Errorf("csr '%s' in work queue no longer exists", key))
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// csr create by kubesphere auto approve
|
||||
if username := csr.Annotations[constants.UsernameAnnotationKey]; username != "" {
|
||||
err = c.Approve(csr)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// certificate data is not empty
|
||||
if len(csr.Status.Certificate) > 0 {
|
||||
err = c.UpdateKubeconfig(csr)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
// release
|
||||
err := c.k8sclient.CertificatesV1beta1().CertificateSigningRequests().Delete(csr.Name, metav1.NewDeleteOptions(0))
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.recorder.Event(csr, corev1.EventTypeNormal, successSynced, messageResourceSynced)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) Start(stopCh <-chan struct{}) error {
|
||||
return c.Run(4, stopCh)
|
||||
}
|
||||
|
||||
func (c *Controller) Approve(csr *certificatesv1beta1.CertificateSigningRequest) error {
|
||||
// is approved
|
||||
if len(csr.Status.Certificate) > 0 {
|
||||
return nil
|
||||
}
|
||||
csr.Status = certificatesv1beta1.CertificateSigningRequestStatus{
|
||||
Conditions: []certificatesv1beta1.CertificateSigningRequestCondition{{
|
||||
Type: "Approved",
|
||||
Reason: "KubeSphereApprove",
|
||||
Message: "This CSR was approved by KubeSphere",
|
||||
LastUpdateTime: metav1.Time{
|
||||
Time: time.Now(),
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
// approve csr
|
||||
csr, err := c.k8sclient.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(csr)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateKubeconfig(csr *certificatesv1beta1.CertificateSigningRequest) error {
|
||||
username := csr.Annotations[constants.UsernameAnnotationKey]
|
||||
|
||||
err := c.kubeconfigOperator.UpdateKubeconfig(username, csr.Status.Certificate)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,219 +1,240 @@
|
||||
/*
|
||||
Copyright 2019 The KubeSphere authors.
|
||||
|
||||
Copyright 2019 The KubeSphere Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package clusterrolebinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
log "k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
|
||||
"reflect"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/informers"
|
||||
rbacv1informers "k8s.io/client-go/informers/rbac/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
rbacv1listers "k8s.io/client-go/listers/rbac/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/models/kubectl"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**
|
||||
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
|
||||
* business logic. Delete these comments after modifying this file.*
|
||||
*/
|
||||
const (
|
||||
// SuccessSynced is used as part of the Event 'reason' when a Foo is synced
|
||||
successSynced = "Synced"
|
||||
// is synced successfully
|
||||
messageResourceSynced = "ClusterRoleBinding synced successfully"
|
||||
controllerName = "clusterrolebinding-controller"
|
||||
)
|
||||
|
||||
// Add creates a new Namespace Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
|
||||
// and Start it when the Manager is Started.
|
||||
func Add(mgr manager.Manager) error {
|
||||
return add(mgr, newReconciler(mgr))
|
||||
type Controller struct {
|
||||
k8sClient kubernetes.Interface
|
||||
informer rbacv1informers.ClusterRoleBindingInformer
|
||||
lister rbacv1listers.ClusterRoleBindingLister
|
||||
synced cache.InformerSynced
|
||||
// workqueue is a rate limited work queue. This is used to queue work to be
|
||||
// processed instead of performing it as soon as a change happens. This
|
||||
// means we can ensure we only process a fixed amount of resources at a
|
||||
// time, and makes it easy to ensure we are never processing the same item
|
||||
// simultaneously in two different workers.
|
||||
workqueue workqueue.RateLimitingInterface
|
||||
// recorder is an event recorder for recording Event resources to the
|
||||
// Kubernetes API.
|
||||
recorder record.EventRecorder
|
||||
kubectlOperator kubectl.Interface
|
||||
}
|
||||
|
||||
// newReconciler returns a new reconcile.Reconciler
|
||||
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
|
||||
return &ReconcileClusterRoleBinding{Client: mgr.GetClient(), scheme: mgr.GetScheme()}
|
||||
func NewController(k8sClient kubernetes.Interface, informerFactory informers.SharedInformerFactory) *Controller {
|
||||
// Create event broadcaster
|
||||
// Add sample-controller types to the default Kubernetes Scheme so Events can be
|
||||
// logged for sample-controller types.
|
||||
|
||||
klog.V(4).Info("Creating event broadcaster")
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(klog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName})
|
||||
informer := informerFactory.Rbac().V1().ClusterRoleBindings()
|
||||
ctl := &Controller{
|
||||
k8sClient: k8sClient,
|
||||
informer: informer,
|
||||
lister: informer.Lister(),
|
||||
synced: informer.Informer().HasSynced,
|
||||
kubectlOperator: kubectl.NewOperator(k8sClient, informerFactory),
|
||||
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ClusterRoleBinding"),
|
||||
recorder: recorder,
|
||||
}
|
||||
klog.Info("Setting up event handlers")
|
||||
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: ctl.enqueueClusterRoleBinding,
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
ctl.enqueueClusterRoleBinding(new)
|
||||
},
|
||||
DeleteFunc: ctl.enqueueClusterRoleBinding,
|
||||
})
|
||||
return ctl
|
||||
}
|
||||
|
||||
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||
func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
// Create a new controller
|
||||
c, err := controller.New("clusterrolebinding-controller", mgr, controller.Options{Reconciler: r})
|
||||
if err != nil {
|
||||
return err
|
||||
func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.workqueue.ShutDown()
|
||||
|
||||
//init client
|
||||
|
||||
// Start the informer factories to begin populating the informer caches
|
||||
klog.Info("Starting User controller")
|
||||
|
||||
// Wait for the caches to be synced before starting workers
|
||||
klog.Info("Waiting for informer caches to sync")
|
||||
if ok := cache.WaitForCacheSync(stopCh, c.synced); !ok {
|
||||
return fmt.Errorf("failed to wait for caches to sync")
|
||||
}
|
||||
|
||||
// Watch for changes to Namespace
|
||||
err = c.Watch(&source.Kind{Type: &rbac.ClusterRoleBinding{}}, &handler.EnqueueRequestForObject{})
|
||||
if err != nil {
|
||||
return err
|
||||
klog.Info("Starting workers")
|
||||
// Launch two workers to process Foo resources
|
||||
for i := 0; i < threadiness; i++ {
|
||||
go wait.Until(c.runWorker, time.Second, stopCh)
|
||||
}
|
||||
|
||||
klog.Info("Started workers")
|
||||
<-stopCh
|
||||
klog.Info("Shutting down workers")
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ reconcile.Reconciler = &ReconcileClusterRoleBinding{}
|
||||
|
||||
// ReconcileClusterRoleBinding reconciles a Namespace object
|
||||
type ReconcileClusterRoleBinding struct {
|
||||
client.Client
|
||||
scheme *runtime.Scheme
|
||||
func (c *Controller) enqueueClusterRoleBinding(obj interface{}) {
|
||||
var key string
|
||||
var err error
|
||||
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
c.workqueue.Add(key)
|
||||
}
|
||||
|
||||
// Reconcile reads that state of the cluster for a Namespace object and makes changes based on the state read
|
||||
// and what is in the Namespace.Spec
|
||||
// +kubebuilder:rbac:groups=core.kubesphere.io,resources=namespaces,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core.kubesphere.io,resources=namespaces/status,verbs=get;update;patch
|
||||
func (r *ReconcileClusterRoleBinding) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
||||
// Fetch the Namespace instance
|
||||
instance := &rbac.ClusterRoleBinding{}
|
||||
if err := r.Get(context.TODO(), request.NamespacedName, instance); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// Object not found, return. Created objects are automatically garbage collected.
|
||||
// For additional cleanup logic use finalizers.
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
// Error reading the object - requeue the request.
|
||||
return reconcile.Result{}, err
|
||||
func (c *Controller) runWorker() {
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
workspaceName := instance.Labels[constants.WorkspaceLabelKey]
|
||||
}
|
||||
|
||||
if workspaceName != "" && k8sutil.IsControlledBy(instance.OwnerReferences, "Workspace", workspaceName) {
|
||||
if instance.Name == getWorkspaceAdminRoleBindingName(workspaceName) ||
|
||||
instance.Name == getWorkspaceViewerRoleBindingName(workspaceName) {
|
||||
nsList := &corev1.NamespaceList{}
|
||||
options := client.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: workspaceName})}
|
||||
func (c *Controller) processNextWorkItem() bool {
|
||||
obj, shutdown := c.workqueue.Get()
|
||||
|
||||
if err := r.List(context.TODO(), nsList, &options); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
for _, ns := range nsList.Items {
|
||||
if !ns.DeletionTimestamp.IsZero() {
|
||||
// skip if the namespace is being deleted
|
||||
continue
|
||||
}
|
||||
if err := r.updateRoleBindings(instance, &ns); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
if shutdown {
|
||||
return false
|
||||
}
|
||||
|
||||
// We wrap this block in a func so we can defer c.workqueue.Done.
|
||||
err := func(obj interface{}) error {
|
||||
// We call Done here so the workqueue knows we have finished
|
||||
// processing this item. We also must remember to call Forget if we
|
||||
// do not want this work item being re-queued. For example, we do
|
||||
// not call Forget if a transient error occurs, instead the item is
|
||||
// put back on the workqueue and attempted again after a back-off
|
||||
// period.
|
||||
defer c.workqueue.Done(obj)
|
||||
var key string
|
||||
var ok bool
|
||||
// We expect strings to come off the workqueue. These are of the
|
||||
// form namespace/name. We do this as the delayed nature of the
|
||||
// workqueue means the items in the informer cache may actually be
|
||||
// more up to date that when the item was initially put onto the
|
||||
// workqueue.
|
||||
if key, ok = obj.(string); !ok {
|
||||
// As the item in the workqueue is actually invalid, we call
|
||||
// Forget here else we'd go into a loop of attempting to
|
||||
// process a work item that is invalid.
|
||||
c.workqueue.Forget(obj)
|
||||
utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
|
||||
return nil
|
||||
}
|
||||
// Run the reconcile, passing it the namespace/name string of the
|
||||
// Foo resource to be synced.
|
||||
if err := c.reconcile(key); err != nil {
|
||||
// Put the item back on the workqueue to handle any transient errors.
|
||||
c.workqueue.AddRateLimited(key)
|
||||
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
|
||||
}
|
||||
// Finally, if no error occurs we Forget this item so it does not
|
||||
// get queued again until another change happens.
|
||||
c.workqueue.Forget(obj)
|
||||
klog.Infof("Successfully synced %s:%s", "key", key)
|
||||
return nil
|
||||
}(obj)
|
||||
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// syncHandler compares the actual state with the desired, and attempts to
|
||||
// converge the two. It then updates the Status block of the Foo resource
|
||||
// with the current status of the resource.
|
||||
func (c *Controller) reconcile(key string) error {
|
||||
|
||||
// Get the clusterRoleBinding with this name
|
||||
clusterRoleBinding, err := c.lister.Get(key)
|
||||
if err != nil {
|
||||
// The user may no longer exist, in which case we stop
|
||||
// processing.
|
||||
if errors.IsNotFound(err) {
|
||||
utilruntime.HandleError(fmt.Errorf("clusterrolebinding '%s' in work queue no longer exists", key))
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
isClusterAdmin := clusterRoleBinding.RoleRef.Name == iamv1alpha2.ClusterAdmin
|
||||
|
||||
if isClusterAdmin {
|
||||
for _, subject := range clusterRoleBinding.Subjects {
|
||||
if subject.Kind == iamv1alpha2.ResourceKindUser {
|
||||
err = c.kubectlOperator.CreateKubectlDeploy(subject.Name)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileClusterRoleBinding) updateRoleBindings(clusterRoleBinding *rbac.ClusterRoleBinding, namespace *corev1.Namespace) error {
|
||||
|
||||
workspaceName := namespace.Labels[constants.WorkspaceLabelKey]
|
||||
|
||||
if clusterRoleBinding.Name == getWorkspaceAdminRoleBindingName(workspaceName) {
|
||||
adminBinding := &rbac.RoleBinding{}
|
||||
adminBinding.Name = "admin"
|
||||
adminBinding.Namespace = namespace.Name
|
||||
adminBinding.RoleRef = rbac.RoleRef{Name: "admin", APIGroup: "rbac.authorization.k8s.io", Kind: "Role"}
|
||||
adminBinding.Subjects = clusterRoleBinding.Subjects
|
||||
|
||||
found := &rbac.RoleBinding{}
|
||||
|
||||
err := r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: adminBinding.Name}, found)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
err = r.Create(context.TODO(), adminBinding)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return err
|
||||
} else if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(found.RoleRef, adminBinding.RoleRef) {
|
||||
err = r.Delete(context.TODO(), found)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("conflict role binding %s.%s, waiting for recreate", namespace.Name, adminBinding.Name)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(found.Subjects, adminBinding.Subjects) {
|
||||
found.Subjects = adminBinding.Subjects
|
||||
err = r.Update(context.TODO(), found)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if clusterRoleBinding.Name == getWorkspaceViewerRoleBindingName(workspaceName) {
|
||||
|
||||
found := &rbac.RoleBinding{}
|
||||
viewerBinding := &rbac.RoleBinding{}
|
||||
viewerBinding.Name = "viewer"
|
||||
viewerBinding.Namespace = namespace.Name
|
||||
viewerBinding.RoleRef = rbac.RoleRef{Name: "viewer", APIGroup: "rbac.authorization.k8s.io", Kind: "Role"}
|
||||
viewerBinding.Subjects = clusterRoleBinding.Subjects
|
||||
|
||||
err := r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: viewerBinding.Name}, found)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
err = r.Create(context.TODO(), viewerBinding)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return err
|
||||
} else if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(found.RoleRef, viewerBinding.RoleRef) {
|
||||
err = r.Delete(context.TODO(), found)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("conflict role binding %s.%s, waiting for recreate", namespace.Name, viewerBinding.Name)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(found.Subjects, viewerBinding.Subjects) {
|
||||
found.Subjects = viewerBinding.Subjects
|
||||
err = r.Update(context.TODO(), found)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.recorder.Event(clusterRoleBinding, corev1.EventTypeNormal, successSynced, messageResourceSynced)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWorkspaceAdminRoleBindingName(workspaceName string) string {
|
||||
return fmt.Sprintf("workspace:%s:admin", workspaceName)
|
||||
func (c *Controller) Start(stopCh <-chan struct{}) error {
|
||||
return c.Run(4, stopCh)
|
||||
}
|
||||
|
||||
func getWorkspaceViewerRoleBindingName(workspaceName string) string {
|
||||
return fmt.Sprintf("workspace:%s:viewer", workspaceName)
|
||||
func encrypt(password string) (string, error) {
|
||||
// when user is already mapped to another identity, password is empty by default
|
||||
// unable to log in directly until password reset
|
||||
if password == "" {
|
||||
return "", nil
|
||||
}
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 The KubeSphere Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
package clusterrolebinding
|
||||
|
||||
import (
|
||||
stdlog "log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"kubesphere.io/kubesphere/pkg/apis"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
t := &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")},
|
||||
}
|
||||
apis.AddToScheme(scheme.Scheme)
|
||||
|
||||
var err error
|
||||
if cfg, err = t.Start(); err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
t.Stop()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and
|
||||
// writes the request to requests after Reconcile is finished.
|
||||
func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) {
|
||||
requests := make(chan reconcile.Request)
|
||||
fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
|
||||
result, err := inner.Reconcile(req)
|
||||
requests <- req
|
||||
return result, err
|
||||
})
|
||||
return fn, requests
|
||||
}
|
||||
|
||||
// StartTestManager adds recFn
|
||||
func StartTestManager(mgr manager.Manager, g *gomega.GomegaWithT) (chan struct{}, *sync.WaitGroup) {
|
||||
stop := make(chan struct{})
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
g.Expect(mgr.Start(stop)).NotTo(gomega.HaveOccurred())
|
||||
}()
|
||||
return stop, wg
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 The KubeSphere Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
package clusterrolebinding
|
||||
@@ -18,6 +18,7 @@ package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
@@ -25,15 +26,18 @@ import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
kubespherescheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
|
||||
userinformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2"
|
||||
userlister "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/models/kubeconfig"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -46,9 +50,9 @@ const (
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
kubeClientset kubernetes.Interface
|
||||
kubesphereClientset kubesphereclient.Interface
|
||||
|
||||
k8sClient kubernetes.Interface
|
||||
ksClient kubesphere.Interface
|
||||
kubeconfig kubeconfig.Interface
|
||||
userInformer userinformer.UserInformer
|
||||
userLister userlister.UserLister
|
||||
userSynced cache.InformerSynced
|
||||
@@ -63,9 +67,8 @@ type Controller struct {
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
func NewController(kubeclientset kubernetes.Interface,
|
||||
kubesphereklientset kubesphereclient.Interface,
|
||||
userInformer userinformer.UserInformer) *Controller {
|
||||
func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface,
|
||||
config *rest.Config, userInformer userinformer.UserInformer) *Controller {
|
||||
// Create event broadcaster
|
||||
// Add sample-controller types to the default Kubernetes Scheme so Events can be
|
||||
// logged for sample-controller types.
|
||||
@@ -74,16 +77,21 @@ func NewController(kubeclientset kubernetes.Interface,
|
||||
klog.V(4).Info("Creating event broadcaster")
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(klog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})
|
||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName})
|
||||
var kubeconfigOperator kubeconfig.Interface
|
||||
if config != nil {
|
||||
kubeconfigOperator = kubeconfig.NewOperator(k8sClient, config, "")
|
||||
}
|
||||
ctl := &Controller{
|
||||
kubeClientset: kubeclientset,
|
||||
kubesphereClientset: kubesphereklientset,
|
||||
userInformer: userInformer,
|
||||
userLister: userInformer.Lister(),
|
||||
userSynced: userInformer.Informer().HasSynced,
|
||||
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Users"),
|
||||
recorder: recorder,
|
||||
k8sClient: k8sClient,
|
||||
ksClient: ksClient,
|
||||
kubeconfig: kubeconfigOperator,
|
||||
userInformer: userInformer,
|
||||
userLister: userInformer.Lister(),
|
||||
userSynced: userInformer.Informer().HasSynced,
|
||||
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Users"),
|
||||
recorder: recorder,
|
||||
}
|
||||
klog.Info("Setting up event handlers")
|
||||
userInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
@@ -205,26 +213,64 @@ func (c *Controller) reconcile(key string) error {
|
||||
utilruntime.HandleError(fmt.Errorf("user '%s' in work queue no longer exists", key))
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.updateUserStatus(user)
|
||||
user, err = c.encryptPassword(user.DeepCopy())
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if c.kubeconfig != nil {
|
||||
err = c.kubeconfig.CreateKubeConfig(user)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.recorder.Event(user, corev1.EventTypeNormal, successSynced, messageResourceSynced)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) updateUserStatus(user *iamv1alpha2.User) error {
|
||||
userCopy := user.DeepCopy()
|
||||
userCopy.Status.State = iamv1alpha2.UserActive
|
||||
_, err := c.kubesphereClientset.IamV1alpha2().Users().Update(userCopy)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Controller) Start(stopCh <-chan struct{}) error {
|
||||
return c.Run(4, stopCh)
|
||||
}
|
||||
|
||||
func (c *Controller) encryptPassword(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
|
||||
encrypted, err := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation])
|
||||
|
||||
// password is not encrypted
|
||||
if err != nil || !encrypted {
|
||||
password, err := encrypt(user.Spec.EncryptedPassword)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
user.Spec.EncryptedPassword = password
|
||||
if user.Annotations == nil {
|
||||
user.Annotations = make(map[string]string, 0)
|
||||
}
|
||||
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = "true"
|
||||
user.Status.State = iamv1alpha2.UserActive
|
||||
|
||||
updated, err := c.ksClient.IamV1alpha2().Users().Update(user)
|
||||
|
||||
return updated, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func encrypt(password string) (string, error) {
|
||||
// when user is already mapped to another identity, password is empty by default
|
||||
// unable to log in directly until password reset
|
||||
if password == "" {
|
||||
return "", nil
|
||||
}
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"k8s.io/client-go/tools/record"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
|
||||
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -44,8 +44,8 @@ var (
|
||||
type fixture struct {
|
||||
t *testing.T
|
||||
|
||||
client *fake.Clientset
|
||||
kubeclient *k8sfake.Clientset
|
||||
ksclient *fake.Clientset
|
||||
k8sclient *k8sfake.Clientset
|
||||
// Objects to put in the store.
|
||||
userLister []*iamv1alpha2.User
|
||||
// Actions expected to happen on the client.
|
||||
@@ -78,25 +78,25 @@ func newUser(name string) *iamv1alpha2.User {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
|
||||
f.client = fake.NewSimpleClientset(f.objects...)
|
||||
f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
|
||||
func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
|
||||
f.ksclient = fake.NewSimpleClientset(f.objects...)
|
||||
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
|
||||
|
||||
i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
|
||||
k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
|
||||
ksinformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
|
||||
k8sinformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
|
||||
|
||||
for _, user := range f.userLister {
|
||||
err := i.Iam().V1alpha2().Users().Informer().GetIndexer().Add(user)
|
||||
err := ksinformers.Iam().V1alpha2().Users().Informer().GetIndexer().Add(user)
|
||||
if err != nil {
|
||||
f.t.Errorf("add user:%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
c := NewController(f.kubeclient, f.client, i.Iam().V1alpha2().Users())
|
||||
c := NewController(f.k8sclient, f.ksclient, nil, ksinformers.Iam().V1alpha2().Users())
|
||||
c.userSynced = alwaysReady
|
||||
c.recorder = &record.FakeRecorder{}
|
||||
|
||||
return c, i, k8sI
|
||||
return c, ksinformers, k8sinformers
|
||||
}
|
||||
|
||||
func (f *fixture) run(userName string) {
|
||||
@@ -123,7 +123,7 @@ func (f *fixture) runController(user string, startInformers bool, expectError bo
|
||||
f.t.Error("expected error syncing user, got nil")
|
||||
}
|
||||
|
||||
actions := filterInformerActions(f.client.Actions())
|
||||
actions := filterInformerActions(f.ksclient.Actions())
|
||||
for i, action := range actions {
|
||||
if len(f.actions) < i+1 {
|
||||
f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
|
||||
@@ -138,7 +138,7 @@ func (f *fixture) runController(user string, startInformers bool, expectError bo
|
||||
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
|
||||
}
|
||||
|
||||
k8sActions := filterInformerActions(f.kubeclient.Actions())
|
||||
k8sActions := filterInformerActions(f.k8sclient.Actions())
|
||||
for i, action := range k8sActions {
|
||||
if len(f.kubeactions) < i+1 {
|
||||
f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[i:])
|
||||
@@ -220,6 +220,7 @@ func filterInformerActions(actions []core.Action) []core.Action {
|
||||
func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) {
|
||||
expect := user.DeepCopy()
|
||||
expect.Status.State = iamv1alpha2.UserActive
|
||||
expect.Annotations = map[string]string{iamv1alpha2.PasswordEncryptedAnnotation: "true"}
|
||||
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
|
||||
f.actions = append(f.actions, action)
|
||||
}
|
||||
|
||||
@@ -20,17 +20,12 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"fmt"
|
||||
"kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
encryptedAnnotation = "iam.kubesphere.io/password-encrypted"
|
||||
)
|
||||
|
||||
type EmailValidator struct {
|
||||
@@ -38,11 +33,6 @@ type EmailValidator struct {
|
||||
decoder *admission.Decoder
|
||||
}
|
||||
|
||||
type PasswordCipher struct {
|
||||
Client client.Client
|
||||
decoder *admission.Decoder
|
||||
}
|
||||
|
||||
func (a *EmailValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
user := &v1alpha2.User{}
|
||||
err := a.decoder.Decode(req, user)
|
||||
@@ -57,11 +47,14 @@ func (a *EmailValidator) Handle(ctx context.Context, req admission.Request) admi
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
if _, err := mail.ParseAddress(user.Spec.Email); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, fmt.Errorf("invalid email address:%s", user.Spec.Email))
|
||||
}
|
||||
|
||||
alreadyExist := emailAlreadyExist(allUsers, user)
|
||||
|
||||
if alreadyExist {
|
||||
return admission.Denied("user email already exists")
|
||||
return admission.Errored(http.StatusConflict, fmt.Errorf("user email: %s already exists", user.Spec.Email))
|
||||
}
|
||||
|
||||
return admission.Allowed("")
|
||||
@@ -76,43 +69,6 @@ func emailAlreadyExist(users v1alpha2.UserList, user *v1alpha2.User) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *PasswordCipher) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
user := &v1alpha2.User{}
|
||||
err := a.decoder.Decode(req, user)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
encrypted, err := strconv.ParseBool(user.Annotations[encryptedAnnotation])
|
||||
|
||||
if err != nil || !encrypted {
|
||||
password, err := hashPassword(user.Spec.EncryptedPassword)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
user.Spec.EncryptedPassword = password
|
||||
user.Annotations[encryptedAnnotation] = "true"
|
||||
}
|
||||
|
||||
marshaledUser, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledUser)
|
||||
}
|
||||
|
||||
func hashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// InjectDecoder injects the decoder.
|
||||
func (a *PasswordCipher) InjectDecoder(d *admission.Decoder) error {
|
||||
a.decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
// InjectDecoder injects the decoder.
|
||||
func (a *EmailValidator) InjectDecoder(d *admission.Decoder) error {
|
||||
a.decoder = d
|
||||
|
||||
Reference in New Issue
Block a user