github.com/oam-dev/cluster-gateway@v1.9.0/pkg/addon/controllers/installer.go (about)

     1  package controllers
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/openshift/library-go/pkg/crypto"
    11  	"github.com/pkg/errors"
    12  	appsv1 "k8s.io/api/apps/v1"
    13  	corev1 "k8s.io/api/core/v1"
    14  	rbacv1 "k8s.io/api/rbac/v1"
    15  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    16  	"k8s.io/apimachinery/pkg/api/meta"
    17  	"k8s.io/apimachinery/pkg/api/resource"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/runtime/schema"
    20  	"k8s.io/apimachinery/pkg/types"
    21  	"k8s.io/apimachinery/pkg/util/intstr"
    22  	"k8s.io/client-go/kubernetes"
    23  	corev1lister "k8s.io/client-go/listers/core/v1"
    24  	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    25  	"k8s.io/utils/pointer"
    26  	"open-cluster-management.io/addon-framework/pkg/certrotation"
    27  	addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
    28  	ocmauthv1alpha1 "open-cluster-management.io/managed-serviceaccount/api/v1alpha1"
    29  	ctrl "sigs.k8s.io/controller-runtime"
    30  	"sigs.k8s.io/controller-runtime/pkg/cache"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/controller-runtime/pkg/handler"
    33  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    34  
    35  	clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
    36  	proxyv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/proxy/v1alpha1"
    37  	"github.com/oam-dev/cluster-gateway/pkg/common"
    38  	"github.com/oam-dev/cluster-gateway/pkg/event"
    39  	"github.com/oam-dev/cluster-gateway/pkg/util/cert"
    40  )
    41  
    42  var (
    43  	log = ctrl.Log.WithName("ClusterGatewayInstaller")
    44  )
    45  var _ reconcile.Reconciler = &ClusterGatewayInstaller{}
    46  
    47  func SetupClusterGatewayInstallerWithManager(mgr ctrl.Manager, caPair *crypto.CA, nativeClient kubernetes.Interface, secretLister corev1lister.SecretLister) error {
    48  	installer := &ClusterGatewayInstaller{
    49  		nativeClient: nativeClient,
    50  		caPair:       caPair,
    51  		secretLister: secretLister,
    52  		cache:        mgr.GetCache(),
    53  		client:       mgr.GetClient(),
    54  		mapper:       mgr.GetRESTMapper(),
    55  	}
    56  	return ctrl.NewControllerManagedBy(mgr).
    57  		// Watches ClusterManagementAddOn singleton
    58  		For(&addonv1alpha1.ClusterManagementAddOn{}).
    59  		// Watches ClusterGatewayConfiguration singleton
    60  		Watches(&proxyv1alpha1.ClusterGatewayConfiguration{},
    61  			&event.ClusterGatewayConfigurationHandler{Client: mgr.GetClient()}).
    62  		// Watches ManagedClusterAddon.
    63  		Watches(&addonv1alpha1.ManagedClusterAddOn{},
    64  			handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &addonv1alpha1.ClusterManagementAddOn{})).
    65  		// Cluster-Gateway mTLS certificate should be actively reconciled
    66  		Watches(&corev1.Secret{},
    67  			handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &addonv1alpha1.ClusterManagementAddOn{})).
    68  		// Secrets rotated by ManagedServiceAccount should be actively reconciled
    69  		Watches(&corev1.Secret{}, &event.SecretHandler{}).
    70  		// Cluster-gateway apiserver instances should be actively reconciled
    71  		Watches(&appsv1.Deployment{},
    72  			handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &addonv1alpha1.ClusterManagementAddOn{})).
    73  		// APIService should be actively reconciled
    74  		Watches(&apiregistrationv1.APIService{}, &event.APIServiceHandler{WatchingName: common.ClusterGatewayAPIServiceName}).
    75  		Complete(installer)
    76  }
    77  
    78  type ClusterGatewayInstaller struct {
    79  	nativeClient kubernetes.Interface
    80  	secretLister corev1lister.SecretLister
    81  	caPair       *crypto.CA
    82  	client       client.Client
    83  	cache        cache.Cache
    84  	mapper       meta.RESTMapper
    85  }
    86  
    87  const (
    88  	SecretNameClusterGatewayTLSCert = "cluster-gateway-tls-cert"
    89  	ServiceNameClusterGateway       = "cluster-gateway"
    90  )
    91  
    92  func (c *ClusterGatewayInstaller) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
    93  	// get the cluster-management-addon instance
    94  	log.Info("Start reconciling")
    95  	addon := &addonv1alpha1.ClusterManagementAddOn{}
    96  	if err := c.client.Get(ctx, request.NamespacedName, addon); err != nil {
    97  		if apierrors.IsNotFound(err) {
    98  			return reconcile.Result{}, nil
    99  		}
   100  		return reconcile.Result{}, errors.Wrapf(err, "failed to get cluster-management-addon: %v", request.Name)
   101  	}
   102  	if addon.Name != common.AddonName {
   103  		// skip
   104  		return reconcile.Result{}, nil
   105  	}
   106  
   107  	if addon.Spec.AddOnConfiguration.CRDName != common.ClusterGatewayConfigurationCRDName {
   108  		// skip
   109  		return reconcile.Result{}, nil
   110  	}
   111  
   112  	clusterGatewayConfiguration := &proxyv1alpha1.ClusterGatewayConfiguration{}
   113  	if err := c.client.Get(ctx, types.NamespacedName{
   114  		Name: addon.Spec.AddOnConfiguration.CRName,
   115  	}, clusterGatewayConfiguration); err != nil {
   116  		if apierrors.IsNotFound(err) {
   117  			return reconcile.Result{}, fmt.Errorf("no such configuration: %v", addon.Spec.AddOnConfiguration.CRName)
   118  		}
   119  		return reconcile.Result{}, fmt.Errorf("failed getting configuration: %v", addon.Spec.AddOnConfiguration.CRName)
   120  	}
   121  
   122  	if err := c.ensureNamespace(clusterGatewayConfiguration.Spec.InstallNamespace); err != nil {
   123  		return reconcile.Result{}, errors.Wrapf(err, "failed to ensure required namespace")
   124  	}
   125  	if err := c.ensureNamespace(clusterGatewayConfiguration.Spec.SecretNamespace); err != nil {
   126  		return reconcile.Result{}, errors.Wrapf(err, "failed to ensure required namespace")
   127  	}
   128  	if err := c.ensureClusterProxySecrets(clusterGatewayConfiguration); err != nil {
   129  		return reconcile.Result{}, errors.Wrapf(err, "failed to ensure required proxy client related credentials")
   130  	}
   131  	if err := c.ensureSecretManagement(addon, clusterGatewayConfiguration); err != nil {
   132  		return reconcile.Result{}, errors.Wrapf(err, "failed to configure secret management")
   133  	}
   134  
   135  	sans := []string{
   136  		ServiceNameClusterGateway,
   137  		ServiceNameClusterGateway + "." + clusterGatewayConfiguration.Spec.InstallNamespace,
   138  		ServiceNameClusterGateway + "." + clusterGatewayConfiguration.Spec.InstallNamespace + ".svc",
   139  	}
   140  	rotation := certrotation.TargetRotation{
   141  		Namespace: clusterGatewayConfiguration.Spec.InstallNamespace,
   142  		Name:      SecretNameClusterGatewayTLSCert,
   143  		HostNames: sans,
   144  		Validity:  time.Hour * 24 * 180,
   145  		Lister:    c.secretLister,
   146  		Client:    c.nativeClient.CoreV1(),
   147  	}
   148  	if err := rotation.EnsureTargetCertKeyPair(c.caPair, c.caPair.Config.Certs); err != nil {
   149  		return reconcile.Result{}, errors.Wrapf(err, "failed rotating server tls cert")
   150  	}
   151  
   152  	caCertData, _, err := c.caPair.Config.GetPEMBytes()
   153  	if err != nil {
   154  		return reconcile.Result{}, errors.Wrapf(err, "failed encoding CA cert")
   155  	}
   156  
   157  	// create if not exists
   158  	namespace := clusterGatewayConfiguration.Spec.InstallNamespace
   159  	targets := []client.Object{
   160  		newServiceAccount(addon, namespace),
   161  		newClusterGatewayService(addon, namespace),
   162  		newAuthenticationRole(addon, namespace),
   163  		newSecretRole(addon, clusterGatewayConfiguration.Spec.SecretNamespace),
   164  		newSecretRoleBinding(addon, namespace, clusterGatewayConfiguration.Spec.SecretNamespace),
   165  		newAPFClusterRole(addon),
   166  		newAPFClusterRoleBinding(addon, namespace),
   167  		newAPIService(addon, namespace, caCertData),
   168  	}
   169  	for _, obj := range targets {
   170  		if err := c.client.Create(context.TODO(), obj); err != nil {
   171  			if !apierrors.IsAlreadyExists(err) {
   172  				return reconcile.Result{}, errors.Wrapf(err, "failed deploying cluster-gateway")
   173  			}
   174  		}
   175  	}
   176  
   177  	if err := c.ensureClusterGatewayDeployment(addon, clusterGatewayConfiguration); err != nil {
   178  		return reconcile.Result{}, errors.Wrapf(err, "failed ensuring cluster-gateway deployment")
   179  	}
   180  
   181  	// always update apiservice
   182  	if err := c.ensureAPIService(addon, namespace); err != nil {
   183  		return reconcile.Result{}, errors.Wrapf(err, "failed ensuring cluster-gateway apiservice")
   184  	}
   185  
   186  	return reconcile.Result{}, nil
   187  }
   188  
   189  func (c *ClusterGatewayInstaller) ensureNamespace(namespace string) error {
   190  	ns := &corev1.Namespace{
   191  		TypeMeta: metav1.TypeMeta{
   192  			APIVersion: "v1",
   193  			Kind:       "Namespace",
   194  		},
   195  		ObjectMeta: metav1.ObjectMeta{
   196  			Name: namespace,
   197  		},
   198  	}
   199  	if _, err := c.nativeClient.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}); err != nil {
   200  		if !apierrors.IsAlreadyExists(err) {
   201  			return err
   202  		}
   203  	}
   204  	return nil
   205  }
   206  
   207  func (c *ClusterGatewayInstaller) ensureAPIService(addon *addonv1alpha1.ClusterManagementAddOn, namespace string) error {
   208  	caCertData, _, err := c.caPair.Config.GetPEMBytes()
   209  	if err != nil {
   210  		return err
   211  	}
   212  	expected := newAPIService(addon, namespace, caCertData)
   213  	current := &apiregistrationv1.APIService{}
   214  	if err := c.client.Get(context.TODO(), types.NamespacedName{
   215  		Name: expected.Name,
   216  	}, current); err != nil {
   217  		return err
   218  	}
   219  	if !bytes.Equal(caCertData, current.Spec.CABundle) {
   220  		expected.ResourceVersion = current.ResourceVersion
   221  		if err := c.client.Update(context.TODO(), expected); err != nil {
   222  			return err
   223  		}
   224  	}
   225  	return nil
   226  }
   227  
   228  func (c *ClusterGatewayInstaller) ensureClusterGatewayDeployment(addon *addonv1alpha1.ClusterManagementAddOn, config *proxyv1alpha1.ClusterGatewayConfiguration) error {
   229  	currentClusterGateway := &appsv1.Deployment{}
   230  	if err := c.client.Get(context.TODO(), types.NamespacedName{
   231  		Namespace: config.Spec.InstallNamespace,
   232  		Name:      "gateway-deployment",
   233  	}, currentClusterGateway); err != nil {
   234  		if apierrors.IsNotFound(err) {
   235  			clusterGateway := newClusterGatewayDeployment(addon, config)
   236  			if err := c.client.Create(context.TODO(), clusterGateway); err != nil {
   237  				return err
   238  			}
   239  			return nil
   240  		}
   241  		return err
   242  	}
   243  	genStr, ok := currentClusterGateway.Labels[labelKeyClusterGatewayConfigurationGeneration]
   244  	if ok {
   245  		gen, err := strconv.Atoi(genStr)
   246  		if err != nil {
   247  			return err
   248  		}
   249  		if config.Generation == int64(gen) {
   250  			return nil
   251  		}
   252  	}
   253  
   254  	clusterGateway := newClusterGatewayDeployment(addon, config)
   255  	clusterGateway.ResourceVersion = currentClusterGateway.ResourceVersion
   256  	if err := c.client.Update(context.TODO(), clusterGateway); err != nil {
   257  		return err
   258  	}
   259  	return nil
   260  }
   261  
   262  func (c *ClusterGatewayInstaller) ensureClusterProxySecrets(config *proxyv1alpha1.ClusterGatewayConfiguration) error {
   263  	if config.Spec.Egress.Type != proxyv1alpha1.EgressTypeClusterProxy {
   264  		return nil
   265  	}
   266  	proxyClientCASecretName := config.Spec.Egress.ClusterProxy.Credentials.ProxyClientCASecretName
   267  	err := cert.CopySecret(c.nativeClient,
   268  		config.Spec.Egress.ClusterProxy.Credentials.Namespace, proxyClientCASecretName,
   269  		config.Spec.InstallNamespace, proxyClientCASecretName)
   270  	if err != nil {
   271  		return errors.Wrapf(err, "failed copy secret %v", proxyClientCASecretName)
   272  	}
   273  	proxyClientSecretName := config.Spec.Egress.ClusterProxy.Credentials.ProxyClientSecretName
   274  	err = cert.CopySecret(c.nativeClient,
   275  		config.Spec.Egress.ClusterProxy.Credentials.Namespace, proxyClientSecretName,
   276  		config.Spec.InstallNamespace, proxyClientSecretName)
   277  	if err != nil {
   278  		return errors.Wrapf(err, "failed copy secret %v", proxyClientSecretName)
   279  	}
   280  	return nil
   281  }
   282  
   283  func (c *ClusterGatewayInstaller) ensureSecretManagement(clusterAddon *addonv1alpha1.ClusterManagementAddOn, config *proxyv1alpha1.ClusterGatewayConfiguration) error {
   284  	if config.Spec.SecretManagement.Type != proxyv1alpha1.SecretManagementTypeManagedServiceAccount {
   285  		return nil
   286  	}
   287  	if _, err := c.mapper.KindFor(schema.GroupVersionResource{
   288  		Group:    ocmauthv1alpha1.GroupVersion.Group,
   289  		Version:  ocmauthv1alpha1.GroupVersion.Version,
   290  		Resource: "managedserviceaccounts",
   291  	}); err != nil {
   292  		return fmt.Errorf("failed to discover ManagedServiceAccount resource in the cluster")
   293  	}
   294  	addonList := &addonv1alpha1.ManagedClusterAddOnList{}
   295  	if err := c.client.List(context.TODO(), addonList); err != nil {
   296  		return errors.Wrapf(err, "failed to list managed cluster addons")
   297  	}
   298  	clusterGatewayAddon := make([]*addonv1alpha1.ManagedClusterAddOn, 0)
   299  	for _, addon := range addonList.Items {
   300  		addon := addon
   301  		if addon.Name == common.AddonName {
   302  			clusterGatewayAddon = append(clusterGatewayAddon, &addon)
   303  		}
   304  	}
   305  	for _, addon := range clusterGatewayAddon {
   306  		managedServiceAccount := buildManagedServiceAccount(addon)
   307  		if err := c.client.Create(context.TODO(), managedServiceAccount); err != nil {
   308  			if !apierrors.IsAlreadyExists(err) {
   309  				return errors.Wrapf(err, "failed to create managed serviceaccount")
   310  			}
   311  		}
   312  
   313  		if err := c.copySecretForManagedServiceAccount(
   314  			clusterAddon,
   315  			config,
   316  			addon.Namespace); err != nil {
   317  			return errors.Wrapf(err, "failed to copy secret from managed serviceaccount")
   318  		}
   319  	}
   320  	return nil
   321  }
   322  
   323  func (c *ClusterGatewayInstaller) copySecretForManagedServiceAccount(addon *addonv1alpha1.ClusterManagementAddOn, config *proxyv1alpha1.ClusterGatewayConfiguration, clusterName string) error {
   324  	endpointType := clusterv1alpha1.ClusterEndpointTypeConst
   325  	if config.Spec.Egress.Type == proxyv1alpha1.EgressTypeClusterProxy {
   326  		endpointType = clusterv1alpha1.ClusterEndpointTypeClusterProxy
   327  	}
   328  	gatewaySecretNamespace := config.Spec.SecretNamespace
   329  	secretName := config.Spec.SecretManagement.ManagedServiceAccount.Name
   330  
   331  	secret, err := c.secretLister.Secrets(clusterName).
   332  		Get(secretName)
   333  	if err != nil {
   334  		if !apierrors.IsNotFound(err) {
   335  			return errors.Wrapf(err, "failed to get token secret")
   336  		}
   337  		return nil
   338  	}
   339  	currentSecret, err := c.secretLister.Secrets(gatewaySecretNamespace).Get(clusterName)
   340  	shouldCreate := false
   341  	if err != nil {
   342  		if !apierrors.IsNotFound(err) {
   343  			return errors.Wrapf(err, "failed to get the cluster secret")
   344  		}
   345  		shouldCreate = true
   346  	}
   347  	if shouldCreate {
   348  		if _, err := c.nativeClient.CoreV1().Secrets(gatewaySecretNamespace).
   349  			Create(context.TODO(),
   350  				&corev1.Secret{
   351  					ObjectMeta: metav1.ObjectMeta{
   352  						Namespace: gatewaySecretNamespace,
   353  						Name:      clusterName,
   354  						Labels: map[string]string{
   355  							common.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeServiceAccountToken),
   356  							common.LabelKeyClusterEndpointType:   string(endpointType),
   357  						},
   358  						OwnerReferences: []metav1.OwnerReference{
   359  							{
   360  								APIVersion: addonv1alpha1.GroupVersion.String(),
   361  								Kind:       "ClusterManagementAddOn",
   362  								UID:        addon.UID,
   363  								Name:       addon.Name,
   364  							},
   365  						},
   366  					},
   367  					Type: corev1.SecretTypeOpaque,
   368  					Data: map[string][]byte{
   369  						corev1.ServiceAccountRootCAKey: secret.Data[corev1.ServiceAccountRootCAKey],
   370  						corev1.ServiceAccountTokenKey:  secret.Data[corev1.ServiceAccountTokenKey],
   371  					},
   372  				},
   373  				metav1.CreateOptions{}); err != nil {
   374  			return errors.Wrapf(err, "failed to create the cluster secret")
   375  		}
   376  	} else {
   377  		if bytes.Equal(secret.Data[corev1.ServiceAccountTokenKey], currentSecret.Data[corev1.ServiceAccountTokenKey]) {
   378  			return nil // no need for an update
   379  		}
   380  		currentSecret.Data[corev1.ServiceAccountRootCAKey] = secret.Data[corev1.ServiceAccountRootCAKey]
   381  		currentSecret.Data[corev1.ServiceAccountTokenKey] = secret.Data[corev1.ServiceAccountTokenKey]
   382  		if _, err := c.nativeClient.CoreV1().Secrets(gatewaySecretNamespace).
   383  			Update(context.TODO(), currentSecret, metav1.UpdateOptions{}); err != nil {
   384  			return errors.Wrapf(err, "failed to update the cluster secret")
   385  		}
   386  	}
   387  	return nil
   388  }
   389  
   390  func newServiceAccount(addon *addonv1alpha1.ClusterManagementAddOn, namespace string) *corev1.ServiceAccount {
   391  	return &corev1.ServiceAccount{
   392  		TypeMeta: metav1.TypeMeta{
   393  			APIVersion: "v1",
   394  			Kind:       "ServiceAccount",
   395  		},
   396  		ObjectMeta: metav1.ObjectMeta{
   397  			Namespace: namespace,
   398  			Name:      common.AddonName,
   399  			OwnerReferences: []metav1.OwnerReference{
   400  				{
   401  					APIVersion: addonv1alpha1.GroupVersion.String(),
   402  					Kind:       "ClusterManagementAddOn",
   403  					UID:        addon.UID,
   404  					Name:       addon.Name,
   405  				},
   406  			},
   407  		},
   408  	}
   409  }
   410  
   411  const labelKeyClusterGatewayConfigurationGeneration = "proxy.open-cluster-management.io/configuration-generation"
   412  
   413  func newClusterGatewayDeployment(addon *addonv1alpha1.ClusterManagementAddOn, config *proxyv1alpha1.ClusterGatewayConfiguration) *appsv1.Deployment {
   414  	args := []string{
   415  		"--secure-port=9443",
   416  		"--secret-namespace=" + config.Spec.SecretNamespace,
   417  		"--ocm-integration=true",
   418  		"--tls-cert-file=/etc/server/tls.crt",
   419  		"--tls-private-key-file=/etc/server/tls.key",
   420  		"--feature-gates=HealthinessCheck=true,SecretCache=true",
   421  	}
   422  	volumes := []corev1.Volume{
   423  		{
   424  			Name: "server",
   425  			VolumeSource: corev1.VolumeSource{
   426  				Secret: &corev1.SecretVolumeSource{
   427  					SecretName: SecretNameClusterGatewayTLSCert,
   428  				},
   429  			},
   430  		},
   431  	}
   432  	volumeMounts := []corev1.VolumeMount{
   433  		{
   434  			Name:      "server",
   435  			MountPath: "/etc/server/",
   436  			ReadOnly:  true,
   437  		},
   438  	}
   439  	if config.Spec.Egress.Type == proxyv1alpha1.EgressTypeClusterProxy {
   440  		args = append(args,
   441  			"--proxy-host="+config.Spec.Egress.ClusterProxy.ProxyServerHost,
   442  			"--proxy-port="+strconv.Itoa(int(config.Spec.Egress.ClusterProxy.ProxyServerPort)),
   443  			"--proxy-ca-cert=/etc/ca/ca.crt",
   444  			"--proxy-cert=/etc/tls/tls.crt",
   445  			"--proxy-key=/etc/tls/tls.key",
   446  		)
   447  		volumes = append(volumes,
   448  			corev1.Volume{
   449  				Name: "proxy-client-ca",
   450  				VolumeSource: corev1.VolumeSource{
   451  					Secret: &corev1.SecretVolumeSource{
   452  						SecretName: "proxy-server-ca",
   453  					},
   454  				},
   455  			},
   456  			corev1.Volume{
   457  				Name: "proxy-client",
   458  				VolumeSource: corev1.VolumeSource{
   459  					Secret: &corev1.SecretVolumeSource{
   460  						SecretName: "proxy-client",
   461  					},
   462  				},
   463  			},
   464  		)
   465  		volumeMounts = append(volumeMounts,
   466  			corev1.VolumeMount{
   467  				Name:      "proxy-client-ca",
   468  				MountPath: "/etc/ca/",
   469  			},
   470  			corev1.VolumeMount{
   471  				Name:      "proxy-client",
   472  				MountPath: "/etc/tls/",
   473  			},
   474  		)
   475  	}
   476  
   477  	maxUnavailable := intstr.FromInt(1)
   478  	maxSurge := intstr.FromInt(1)
   479  	deploy := &appsv1.Deployment{
   480  		TypeMeta: metav1.TypeMeta{
   481  			APIVersion: "apps/v1",
   482  			Kind:       "Deployment",
   483  		},
   484  		ObjectMeta: metav1.ObjectMeta{
   485  			Namespace: config.Spec.InstallNamespace,
   486  			Name:      "gateway-deployment",
   487  			OwnerReferences: []metav1.OwnerReference{
   488  				{
   489  					APIVersion: addonv1alpha1.GroupVersion.String(),
   490  					Kind:       "ClusterManagementAddOn",
   491  					UID:        addon.UID,
   492  					Name:       addon.Name,
   493  				},
   494  			},
   495  			Labels: map[string]string{
   496  				labelKeyClusterGatewayConfigurationGeneration: strconv.Itoa(int(config.Generation)),
   497  			},
   498  		},
   499  		Spec: appsv1.DeploymentSpec{
   500  			Selector: &metav1.LabelSelector{
   501  				MatchLabels: map[string]string{
   502  					common.LabelKeyOpenClusterManagementAddon: common.AddonName,
   503  				},
   504  			},
   505  			Replicas: pointer.Int32(3),
   506  			Template: corev1.PodTemplateSpec{
   507  				ObjectMeta: metav1.ObjectMeta{
   508  					Labels: map[string]string{
   509  						common.LabelKeyOpenClusterManagementAddon: common.AddonName,
   510  					},
   511  				},
   512  				Spec: corev1.PodSpec{
   513  					Containers: []corev1.Container{
   514  						{
   515  							Name:            "apiserver",
   516  							Image:           config.Spec.Image,
   517  							ImagePullPolicy: corev1.PullIfNotPresent,
   518  							Args:            args,
   519  							VolumeMounts:    volumeMounts,
   520  							Resources: corev1.ResourceRequirements{
   521  								Requests: corev1.ResourceList{
   522  									corev1.ResourceCPU:    *resource.NewMilliQuantity(100, resource.DecimalSI),
   523  									corev1.ResourceMemory: *resource.NewQuantity(200*1024*1024, resource.BinarySI),
   524  								},
   525  								Limits: corev1.ResourceList{
   526  									corev1.ResourceCPU:    *resource.NewMilliQuantity(500, resource.DecimalSI),
   527  									corev1.ResourceMemory: *resource.NewQuantity(600*1024*1024, resource.BinarySI),
   528  								},
   529  							},
   530  						},
   531  					},
   532  					ServiceAccountName: common.AddonName,
   533  					Volumes:            volumes,
   534  				},
   535  			},
   536  			Strategy: appsv1.DeploymentStrategy{
   537  				Type: appsv1.RollingUpdateDeploymentStrategyType,
   538  				RollingUpdate: &appsv1.RollingUpdateDeployment{
   539  
   540  					MaxUnavailable: &maxUnavailable,
   541  					MaxSurge:       &maxSurge,
   542  				},
   543  			},
   544  		},
   545  	}
   546  	return deploy
   547  }
   548  
   549  func newClusterGatewayService(addon *addonv1alpha1.ClusterManagementAddOn, namespace string) *corev1.Service {
   550  	return &corev1.Service{
   551  		TypeMeta: metav1.TypeMeta{
   552  			APIVersion: "v1",
   553  			Kind:       "Service",
   554  		},
   555  		ObjectMeta: metav1.ObjectMeta{
   556  			Namespace: namespace,
   557  			Name:      common.AddonName,
   558  			OwnerReferences: []metav1.OwnerReference{
   559  				{
   560  					APIVersion: addonv1alpha1.GroupVersion.String(),
   561  					Kind:       "ClusterManagementAddOn",
   562  					UID:        addon.UID,
   563  					Name:       addon.Name,
   564  				},
   565  			},
   566  		},
   567  		Spec: corev1.ServiceSpec{
   568  			Type: corev1.ServiceTypeClusterIP,
   569  			Selector: map[string]string{
   570  				common.LabelKeyOpenClusterManagementAddon: common.AddonName,
   571  			},
   572  			Ports: []corev1.ServicePort{
   573  				{
   574  					Name: "https",
   575  					Port: 9443,
   576  				},
   577  			},
   578  		},
   579  	}
   580  }
   581  
   582  func newAPIService(addon *addonv1alpha1.ClusterManagementAddOn, namespace string, verifyingCABundle []byte) *apiregistrationv1.APIService {
   583  	return &apiregistrationv1.APIService{
   584  		ObjectMeta: metav1.ObjectMeta{
   585  			Name: "v1alpha1.cluster.core.oam.dev",
   586  			OwnerReferences: []metav1.OwnerReference{
   587  				{
   588  					APIVersion: addonv1alpha1.GroupVersion.String(),
   589  					Kind:       "ClusterManagementAddOn",
   590  					UID:        addon.UID,
   591  					Name:       addon.Name,
   592  				},
   593  			},
   594  		},
   595  		Spec: apiregistrationv1.APIServiceSpec{
   596  			Group:   "cluster.core.oam.dev",
   597  			Version: "v1alpha1",
   598  			Service: &apiregistrationv1.ServiceReference{
   599  				Namespace: namespace,
   600  				Name:      common.AddonName,
   601  				Port:      pointer.Int32(9443),
   602  			},
   603  			GroupPriorityMinimum: 5000,
   604  			VersionPriority:      10,
   605  			CABundle:             verifyingCABundle,
   606  		},
   607  	}
   608  }
   609  
   610  func newAuthenticationRole(addon *addonv1alpha1.ClusterManagementAddOn, namespace string) *rbacv1.RoleBinding {
   611  	return &rbacv1.RoleBinding{
   612  		ObjectMeta: metav1.ObjectMeta{
   613  			Namespace: "kube-system",
   614  			Name:      "extension-apiserver-authentication-reader:cluster-gateway",
   615  			OwnerReferences: []metav1.OwnerReference{
   616  				{
   617  					APIVersion: addonv1alpha1.GroupVersion.String(),
   618  					Kind:       "ClusterManagementAddOn",
   619  					UID:        addon.UID,
   620  					Name:       addon.Name,
   621  				},
   622  			},
   623  		},
   624  		RoleRef: rbacv1.RoleRef{
   625  			Kind: "Role",
   626  			Name: "extension-apiserver-authentication-reader",
   627  		},
   628  		Subjects: []rbacv1.Subject{
   629  			{
   630  				Kind:      rbacv1.ServiceAccountKind,
   631  				Namespace: namespace,
   632  				Name:      common.AddonName,
   633  			},
   634  		},
   635  	}
   636  }
   637  
   638  func newSecretRole(addon *addonv1alpha1.ClusterManagementAddOn, secretNamespace string) *rbacv1.Role {
   639  	return &rbacv1.Role{
   640  		ObjectMeta: metav1.ObjectMeta{
   641  			Namespace: secretNamespace,
   642  			Name:      "cluster-gateway",
   643  			OwnerReferences: []metav1.OwnerReference{
   644  				{
   645  					APIVersion: addonv1alpha1.GroupVersion.String(),
   646  					Kind:       "ClusterManagementAddOn",
   647  					UID:        addon.UID,
   648  					Name:       addon.Name,
   649  				},
   650  			},
   651  		},
   652  		Rules: []rbacv1.PolicyRule{
   653  			{
   654  				APIGroups: []string{""},
   655  				Resources: []string{"secrets"},
   656  				Verbs:     []string{"get", "list", "watch", "update"},
   657  			},
   658  		},
   659  	}
   660  }
   661  
   662  func newSecretRoleBinding(addon *addonv1alpha1.ClusterManagementAddOn, namespace, secretNamespace string) *rbacv1.RoleBinding {
   663  	return &rbacv1.RoleBinding{
   664  		ObjectMeta: metav1.ObjectMeta{
   665  			Namespace: secretNamespace,
   666  			Name:      "cluster-gateway",
   667  			OwnerReferences: []metav1.OwnerReference{
   668  				{
   669  					APIVersion: addonv1alpha1.GroupVersion.String(),
   670  					Kind:       "ClusterManagementAddOn",
   671  					UID:        addon.UID,
   672  					Name:       addon.Name,
   673  				},
   674  			},
   675  		},
   676  		RoleRef: rbacv1.RoleRef{
   677  			Kind: "Role",
   678  			Name: "cluster-gateway",
   679  		},
   680  		Subjects: []rbacv1.Subject{
   681  			{
   682  				Kind:      rbacv1.ServiceAccountKind,
   683  				Namespace: namespace,
   684  				Name:      common.AddonName,
   685  			},
   686  		},
   687  	}
   688  }
   689  func newAPFClusterRole(addon *addonv1alpha1.ClusterManagementAddOn) *rbacv1.ClusterRole {
   690  	return &rbacv1.ClusterRole{
   691  		ObjectMeta: metav1.ObjectMeta{
   692  			Name: "apiserver-aggregation:cluster-gateway",
   693  			OwnerReferences: []metav1.OwnerReference{
   694  				{
   695  					APIVersion: addonv1alpha1.GroupVersion.String(),
   696  					Kind:       "ClusterManagementAddOn",
   697  					UID:        addon.UID,
   698  					Name:       addon.Name,
   699  				},
   700  			},
   701  		},
   702  		Rules: []rbacv1.PolicyRule{
   703  			{
   704  				APIGroups: []string{"cluster.open-cluster-management.io"},
   705  				Resources: []string{"managedclusters"},
   706  				Verbs:     []string{"get", "list", "watch"},
   707  			},
   708  			{
   709  				APIGroups: []string{""},
   710  				Resources: []string{"namespaces"},
   711  				Verbs:     []string{"get", "list", "watch"},
   712  			},
   713  			{
   714  				APIGroups: []string{"admissionregistration.k8s.io"},
   715  				Resources: []string{"mutatingwebhookconfigurations", "validatingwebhookconfigurations"},
   716  				Verbs:     []string{"get", "list", "watch"},
   717  			},
   718  			{
   719  				APIGroups: []string{"flowcontrol.apiserver.k8s.io"},
   720  				Resources: []string{"prioritylevelconfigurations", "flowschemas"},
   721  				Verbs:     []string{"get", "list", "watch"},
   722  			},
   723  			{
   724  				APIGroups: []string{"authorization.k8s.io"},
   725  				Resources: []string{"subjectaccessreviews"},
   726  				Verbs:     []string{"*"},
   727  			},
   728  		},
   729  	}
   730  }
   731  
   732  func newAPFClusterRoleBinding(addon *addonv1alpha1.ClusterManagementAddOn, namespace string) *rbacv1.ClusterRoleBinding {
   733  	return &rbacv1.ClusterRoleBinding{
   734  		ObjectMeta: metav1.ObjectMeta{
   735  			Name: "apiserver-aggregation:cluster-gateway",
   736  			OwnerReferences: []metav1.OwnerReference{
   737  				{
   738  					APIVersion: addonv1alpha1.GroupVersion.String(),
   739  					Kind:       "ClusterManagementAddOn",
   740  					UID:        addon.UID,
   741  					Name:       addon.Name,
   742  				},
   743  			},
   744  		},
   745  		RoleRef: rbacv1.RoleRef{
   746  			Kind: "ClusterRole",
   747  			Name: "apiserver-aggregation:cluster-gateway",
   748  		},
   749  		Subjects: []rbacv1.Subject{
   750  			{
   751  				Kind:      rbacv1.ServiceAccountKind,
   752  				Namespace: namespace,
   753  				Name:      common.AddonName,
   754  			},
   755  		},
   756  	}
   757  }
   758  
   759  func buildManagedServiceAccount(addon *addonv1alpha1.ManagedClusterAddOn) *ocmauthv1alpha1.ManagedServiceAccount {
   760  	return &ocmauthv1alpha1.ManagedServiceAccount{
   761  		TypeMeta: metav1.TypeMeta{
   762  			APIVersion: "authentication.open-cluster-management.io/v1alpha1",
   763  			Kind:       "ManagedServiceAccount",
   764  		},
   765  		ObjectMeta: metav1.ObjectMeta{
   766  			Namespace: addon.Namespace,
   767  			Name:      common.AddonName,
   768  			OwnerReferences: []metav1.OwnerReference{
   769  				{
   770  					APIVersion: addonv1alpha1.GroupVersion.String(),
   771  					Kind:       "ManagedClusterAddOn",
   772  					UID:        addon.UID,
   773  					Name:       addon.Name,
   774  				},
   775  			},
   776  		},
   777  		Spec: ocmauthv1alpha1.ManagedServiceAccountSpec{
   778  			Rotation: ocmauthv1alpha1.ManagedServiceAccountRotation{
   779  				Enabled: true,
   780  				Validity: metav1.Duration{
   781  					Duration: time.Hour * 24 * 180,
   782  				},
   783  			},
   784  		},
   785  	}
   786  }