From b27be869d540e2e996e02e0c451f2883cf3c107b Mon Sep 17 00:00:00 2001 From: savitaashture Date: Mon, 20 Sep 2021 11:01:36 +0530 Subject: [PATCH] [Openshift] Admin user can disable auto creation of RBAC resources --- .../all/operator_v1alpha1_config_cr.yaml | 3 + pkg/apis/operator/v1alpha1/common.go | 4 +- .../operator/v1alpha1/tektonconfig_types.go | 7 + .../v1alpha1/zz_generated.deepcopy.go | 12 ++ .../openshift/tektonconfig/common.go | 135 ++++++++++++++++ .../openshift/tektonconfig/extension.go | 40 +++-- pkg/reconciler/openshift/tektonconfig/rbac.go | 150 +++++++++++++----- .../golang-lru/simplelru/lru_interface.go | 4 +- 8 files changed, 306 insertions(+), 49 deletions(-) create mode 100644 pkg/reconciler/openshift/tektonconfig/common.go diff --git a/config/crs/openshift/config/all/operator_v1alpha1_config_cr.yaml b/config/crs/openshift/config/all/operator_v1alpha1_config_cr.yaml index 38432513c6..cfa439a020 100644 --- a/config/crs/openshift/config/all/operator_v1alpha1_config_cr.yaml +++ b/config/crs/openshift/config/all/operator_v1alpha1_config_cr.yaml @@ -25,3 +25,6 @@ spec: value: "true" - name: pipelineTemplates value: "true" + params: + - name: createRbacResource + value: "true" diff --git a/pkg/apis/operator/v1alpha1/common.go b/pkg/apis/operator/v1alpha1/common.go index 0ed757868b..a17fb7312c 100644 --- a/pkg/apis/operator/v1alpha1/common.go +++ b/pkg/apis/operator/v1alpha1/common.go @@ -94,8 +94,8 @@ func (c *CommonSpec) GetTargetNamespace() string { // Param declares an string value to use for the parameter called name. type Param struct { - Name string `json:"name"` - Value string `json:"value"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` } // ParamValue defines a default value and possible values for a param diff --git a/pkg/apis/operator/v1alpha1/tektonconfig_types.go b/pkg/apis/operator/v1alpha1/tektonconfig_types.go index 4e47d4ef81..10a9e206fc 100644 --- a/pkg/apis/operator/v1alpha1/tektonconfig_types.go +++ b/pkg/apis/operator/v1alpha1/tektonconfig_types.go @@ -84,6 +84,9 @@ type TektonConfigSpec struct { // Dashboard holds the customizable options for dashboards component // +optional Dashboard Dashboard `json:"dashboard,omitempty"` + // Params is the list of params passed for all platforms + // +optional + Params []Param `json:"params,omitempty"` } // TektonConfigStatus defines the observed state of TektonConfig @@ -97,6 +100,10 @@ type TektonConfigStatus struct { // The version of the installed release // +optional Version string `json:"version,omitempty"` + + // The current installer set name + // +optional + TektonInstallerSet map[string]string `json:"tektonInstallerSets,omitempty"` } // TektonConfigList contains a list of TektonConfig diff --git a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go index d059aa1dea..c153665edd 100644 --- a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go @@ -505,6 +505,11 @@ func (in *TektonConfigSpec) DeepCopyInto(out *TektonConfigSpec) { in.Pipeline.DeepCopyInto(&out.Pipeline) out.Trigger = in.Trigger out.Dashboard = in.Dashboard + if in.Params != nil { + in, out := &in.Params, &out.Params + *out = make([]Param, len(*in)) + copy(*out, *in) + } return } @@ -522,6 +527,13 @@ func (in *TektonConfigSpec) DeepCopy() *TektonConfigSpec { func (in *TektonConfigStatus) DeepCopyInto(out *TektonConfigStatus) { *out = *in in.Status.DeepCopyInto(&out.Status) + if in.TektonInstallerSet != nil { + in, out := &in.TektonInstallerSet, &out.TektonInstallerSet + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } diff --git a/pkg/reconciler/openshift/tektonconfig/common.go b/pkg/reconciler/openshift/tektonconfig/common.go new file mode 100644 index 0000000000..9295039527 --- /dev/null +++ b/pkg/reconciler/openshift/tektonconfig/common.go @@ -0,0 +1,135 @@ +/* +Copyright 2021 The Tekton 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 tektonconfig + +import ( + "context" + + "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" + "github.com/tektoncd/operator/pkg/client/clientset/versioned" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func createInstallerSet(ctx context.Context, oc versioned.Interface, tc *v1alpha1.TektonConfig, labels map[string]string, + releaseVersion, component, installerSetName string) error { + + is := makeInstallerSet(tc, installerSetName, releaseVersion, labels) + + createdIs, err := oc.OperatorV1alpha1().TektonInstallerSets(). + Create(ctx, is, metav1.CreateOptions{}) + if err != nil && !errors.IsAlreadyExists(err) { + return err + } + + if len(tc.Status.TektonInstallerSet) == 0 { + tc.Status.TektonInstallerSet = map[string]string{} + } + + // Update the status of tektonConfig with created installerSet name + tc.Status.TektonInstallerSet[component] = createdIs.Name + tc.Status.SetVersion(releaseVersion) + + _, err = oc.OperatorV1alpha1().TektonConfigs(). + UpdateStatus(ctx, tc, metav1.UpdateOptions{}) + + return err +} + +func makeInstallerSet(tc *v1alpha1.TektonConfig, name, releaseVersion string, labels map[string]string) *v1alpha1.TektonInstallerSet { + ownerRef := *metav1.NewControllerRef(tc, tc.GetGroupVersionKind()) + return &v1alpha1.TektonInstallerSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: labels, + Annotations: map[string]string{ + releaseVersionKey: releaseVersion, + targetNamespaceKey: tc.Spec.TargetNamespace, + }, + OwnerReferences: []metav1.OwnerReference{ownerRef}, + }, + } +} + +func deleteInstallerSet(ctx context.Context, oc versioned.Interface, tc *v1alpha1.TektonConfig, component string) error { + + compInstallerSet, ok := tc.Status.TektonInstallerSet[component] + if !ok { + return nil + } + + if compInstallerSet != "" { + // delete the installer set + err := oc.OperatorV1alpha1().TektonInstallerSets(). + Delete(ctx, tc.Status.TektonInstallerSet[component], metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + return err + } + + // clear the name of installer set from TektonConfig status + delete(tc.Status.TektonInstallerSet, component) + _, err = oc.OperatorV1alpha1().TektonConfigs(). + UpdateStatus(ctx, tc, metav1.UpdateOptions{}) + if err != nil && !errors.IsNotFound(err) { + return err + } + } + + return nil +} + +// checkIfInstallerSetExist checks if installer set exists for a component and return true/false based on it +// and if installer set which already exist is of older version then it deletes and return false to create a new +// installer set +func checkIfInstallerSetExist(ctx context.Context, oc versioned.Interface, relVersion string, + tc *v1alpha1.TektonConfig, component string) (bool, error) { + + // Check if installer set is already created + compInstallerSet, ok := tc.Status.TektonInstallerSet[component] + if !ok { + return false, nil + } + + if compInstallerSet != "" { + // if already created then check which version it is + ctIs, err := oc.OperatorV1alpha1().TektonInstallerSets(). + Get(ctx, compInstallerSet, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + + if version, ok := ctIs.Annotations[releaseVersionKey]; ok && version == relVersion { + // if installer set already exist and release version is same + // then ignore and move on + return true, nil + } + + // release version doesn't exist or is different from expected + // deleted existing InstallerSet and create a new one + + err = oc.OperatorV1alpha1().TektonInstallerSets(). + Delete(ctx, compInstallerSet, metav1.DeleteOptions{}) + if err != nil { + return false, err + } + } + + return false, nil +} diff --git a/pkg/reconciler/openshift/tektonconfig/extension.go b/pkg/reconciler/openshift/tektonconfig/extension.go index 7cf46571f1..ecb7896d8b 100644 --- a/pkg/reconciler/openshift/tektonconfig/extension.go +++ b/pkg/reconciler/openshift/tektonconfig/extension.go @@ -56,22 +56,44 @@ func (oe openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Tr func (oe openshiftExtension) PreReconcile(ctx context.Context, tc v1alpha1.TektonComponent) error { config := tc.(*v1alpha1.TektonConfig) + r := rbac{ + kubeClientSet: oe.kubeClientSet, + operatorClientSet: oe.operatorClientSet, + version: os.Getenv(versionKey), + tektonConfig: config, + } + pipelineUpdated := openshiftPipeline.SetDefault(&config.Spec.Pipeline) triggerUpdated := openshiftTrigger.SetDefault(&config.Spec.Trigger.TriggersProperties) - if pipelineUpdated || triggerUpdated { + if pipelineUpdated || triggerUpdated || r.setDefault() { if _, err := oe.operatorClientSet.OperatorV1alpha1().TektonConfigs().Update(ctx, config, v1.UpdateOptions{}); err != nil { return err } } - r := rbac{ - kubeClientSet: oe.kubeClientSet, - operatorClientSet: oe.operatorClientSet, - ownerRef: configOwnerRef(tc), - version: os.Getenv(versionKey), + createRBACResource := true + for _, v := range config.Spec.Params { + // check for param name and if its matches to createRbacResource + // then disable auto creation of RBAC resources by deleting installerSet + if v.Name == rbacParamName && v.Value == "false" { + createRBACResource = false + if err := deleteInstallerSet(ctx, r.operatorClientSet, r.tektonConfig, componentName); err != nil { + return err + } + // remove openshift-pipelines.tekton.dev/namespace-reconcile-version label from namespaces while deleting RBAC resources. + if err := r.cleanUp(ctx); err != nil { + return err + } + } + } + + if createRBACResource { + return r.createResources(ctx) } - return r.createResources(ctx) + + return nil } + func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.TektonComponent) error { configInstance := comp.(*v1alpha1.TektonConfig) @@ -103,6 +125,6 @@ func (oe openshiftExtension) Finalize(ctx context.Context, comp v1alpha1.TektonC } // configOwnerRef returns owner reference pointing to passed instance -func configOwnerRef(tc v1alpha1.TektonComponent) metav1.OwnerReference { - return *metav1.NewControllerRef(tc, tc.GroupVersionKind()) +func configOwnerRef(tc v1alpha1.TektonInstallerSet) metav1.OwnerReference { + return *metav1.NewControllerRef(&tc, tc.GetGroupVersionKind()) } diff --git a/pkg/reconciler/openshift/tektonconfig/rbac.go b/pkg/reconciler/openshift/tektonconfig/rbac.go index 7b1816930f..28308d046a 100644 --- a/pkg/reconciler/openshift/tektonconfig/rbac.go +++ b/pkg/reconciler/openshift/tektonconfig/rbac.go @@ -21,6 +21,7 @@ import ( "fmt" "regexp" + "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" clientset "github.com/tektoncd/operator/pkg/client/clientset/versioned" "github.com/tektoncd/operator/pkg/reconciler/common" corev1 "k8s.io/api/core/v1" @@ -41,6 +42,12 @@ const ( trustedCABundleConfigMap = "config-trusted-cabundle" clusterInterceptors = "openshift-pipelines-clusterinterceptors" namespaceVersionLabel = "openshift-pipelines.tekton.dev/namespace-reconcile-version" + createdByKey = "operator.tekton.dev/created-by" + createdByValue = "RBAC" + releaseVersionKey = "operator.tekton.dev/release-version" + targetNamespaceKey = "operator.tekton.dev/target-namespace" + componentName = "rhosp-rbac" + rbacParamName = "createRbacResource" ) // Namespace Regex to ignore the namespace for creating rbac resources. @@ -51,12 +58,13 @@ type rbac struct { operatorClientSet clientset.Interface ownerRef metav1.OwnerReference version string + tektonConfig *v1alpha1.TektonConfig } func (r *rbac) cleanUp(ctx context.Context) error { // fetch the list of all namespaces which have label - // `openshift-pipelines.tekton.dev/namespace-ready: ` + // `openshift-pipelines.tekton.dev/namespace-reconcile-version: ` namespaces, err := r.kubeClientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("%s = %s", namespaceVersionLabel, r.version), }) @@ -75,12 +83,39 @@ func (r *rbac) cleanUp(ctx context.Context) error { return nil } +func (r *rbac) setDefault() bool { + var ( + updated = false + found = false + ) + + for i, v := range r.tektonConfig.Spec.Params { + if v.Name == rbacParamName { + found = true + // If the value set is invalid then set key to default value as true. + if v.Value != "false" && v.Value != "true" { + r.tektonConfig.Spec.Params[i].Value = "true" + updated = true + } + break + } + } + if !found { + r.tektonConfig.Spec.Params = append(r.tektonConfig.Spec.Params, v1alpha1.Param{ + Name: rbacParamName, + Value: "true", + }) + updated = true + } + return updated +} + func (r *rbac) createResources(ctx context.Context) error { logger := logging.FromContext(ctx) // fetch the list of all namespaces which doesn't have label - // `openshift-pipelines.tekton.dev/namespace-ready: ` + // `openshift-pipelines.tekton.dev/namespace-reconcile-version: ` namespaces, err := r.kubeClientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("%s != %s", namespaceVersionLabel, r.version), }) @@ -104,6 +139,26 @@ func (r *rbac) createResources(ctx context.Context) error { return nil } + exist, err := checkIfInstallerSetExist(ctx, r.operatorClientSet, r.version, r.tektonConfig, componentName) + if err != nil { + return err + } + if !exist { + if err := createInstallerSet(ctx, r.operatorClientSet, r.tektonConfig, map[string]string{ + createdByKey: createdByValue, + }, r.version, componentName, "rbac-resources"); err != nil { + return err + } + } + + getdIs, err := r.operatorClientSet.OperatorV1alpha1().TektonInstallerSets(). + Get(ctx, "rbac-resources", metav1.GetOptions{}) + if err != nil { + return err + } + + r.ownerRef = configOwnerRef(*getdIs) + // Maintaining a separate cluster role for the scc declaration. // to assist us in managing this the scc association in a // granular way. @@ -136,7 +191,7 @@ func (r *rbac) createResources(ctx context.Context) error { return err } - // Add `openshift-pipelines.tekton.dev/namespace-ready` label to namespace + // Add `openshift-pipelines.tekton.dev/namespace-reconcile-version` label to namespace // so that rbac won't loop on it again nsLabels := n.GetLabels() if len(nsLabels) == 0 { @@ -168,12 +223,11 @@ func (r *rbac) ensureCABundles(ctx context.Context, ns *corev1.Namespace) error return err } } - // set owner reference if not set - if err == nil && len(caBundleCM.GetOwnerReferences()) == 0 { - caBundleCM.SetOwnerReferences([]metav1.OwnerReference{r.ownerRef}) - if _, err := cfgInterface.Update(ctx, caBundleCM, metav1.UpdateOptions{}); err != nil { - return err - } + // set owner reference if not set or update owner reference if different owners are set + caBundleCM.SetOwnerReferences(r.updateOwnerRefs(caBundleCM.GetOwnerReferences())) + + if _, err = cfgInterface.Update(ctx, caBundleCM, metav1.UpdateOptions{}); err != nil { + return err } // Ensure service CA bundle @@ -188,12 +242,10 @@ func (r *rbac) ensureCABundles(ctx context.Context, ns *corev1.Namespace) error return err } } - // set owner reference if not set - if err == nil && len(serviceCABundleCM.GetOwnerReferences()) == 0 { - serviceCABundleCM.SetOwnerReferences([]metav1.OwnerReference{r.ownerRef}) - if _, err := cfgInterface.Update(ctx, serviceCABundleCM, metav1.UpdateOptions{}); err != nil { - return err - } + // set owner reference if not set or update owner reference if different owners are set + serviceCABundleCM.SetOwnerReferences(r.updateOwnerRefs(serviceCABundleCM.GetOwnerReferences())) + if _, err := cfgInterface.Update(ctx, serviceCABundleCM, metav1.UpdateOptions{}); err != nil { + return err } return nil @@ -257,12 +309,10 @@ func (r *rbac) ensureSA(ctx context.Context, ns *corev1.Namespace) (*corev1.Serv return createSA(ctx, saInterface, ns.Name, r.ownerRef) } - if len(sa.GetOwnerReferences()) == 0 { - sa.SetOwnerReferences([]metav1.OwnerReference{r.ownerRef}) - return saInterface.Update(ctx, sa, metav1.UpdateOptions{}) - } + // set owner reference if not set or update owner reference if different owners are set + sa.SetOwnerReferences(r.updateOwnerRefs(sa.GetOwnerReferences())) - return sa, nil + return saInterface.Update(ctx, sa, metav1.UpdateOptions{}) } func createSA(ctx context.Context, saInterface v1.ServiceAccountInterface, ns string, ownerRef metav1.OwnerReference) (*corev1.ServiceAccount, error) { @@ -311,15 +361,13 @@ func (r *rbac) ensurePipelinesSCClusterRole(ctx context.Context) error { } rbacClient := r.kubeClientSet.RbacV1() - _, err := rbacClient.ClusterRoles().Get(ctx, pipelinesSCCClusterRole, metav1.GetOptions{}) - - if err != nil { + if _, err := rbacClient.ClusterRoles().Get(ctx, pipelinesSCCClusterRole, metav1.GetOptions{}); err != nil { if errors.IsNotFound(err) { _, err = rbacClient.ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{}) } return err } - _, err = rbacClient.ClusterRoles().Update(ctx, clusterRole, metav1.UpdateOptions{}) + _, err := rbacClient.ClusterRoles().Update(ctx, clusterRole, metav1.UpdateOptions{}) return err } @@ -380,16 +428,26 @@ func (r *rbac) updateRoleBinding(ctx context.Context, rb *rbacv1.RoleBinding, sa rb.Subjects = append(rb.Subjects, subject) } + rbacClient := r.kubeClientSet.RbacV1() + hasOwnerRef := hasOwnerRefernce(rb.GetOwnerReferences(), r.ownerRef) + ownerRef := r.updateOwnerRefs(rb.GetOwnerReferences()) rb.SetOwnerReferences(ownerRef) + // If owners are different then we need to set from r.ownerRef and update the roleBinding. + if !hasOwnerRef { + if _, err := rbacClient.RoleBindings(sa.Namespace).Update(ctx, rb, metav1.UpdateOptions{}); err != nil { + logger.Error(err, "failed to update edit rb") + return err + } + } + if hasSubject && (len(ownerRef) != 0) { - logger.Info("rolebinding is up to date", "action", "none") + logger.Info("rolebinding is up to date ", "action ", "none") return nil } logger.Info("update existing rolebinding edit") - rbacClient := r.kubeClientSet.RbacV1() _, err := rbacClient.RoleBindings(sa.Namespace).Update(ctx, rb, metav1.UpdateOptions{}) if err != nil { @@ -409,6 +467,15 @@ func hasSubject(subjects []rbacv1.Subject, x rbacv1.Subject) bool { return false } +func hasOwnerRefernce(old []metav1.OwnerReference, new metav1.OwnerReference) bool { + for _, v := range old { + if v.APIVersion == new.APIVersion && v.Kind == new.Kind && v.Name == new.Name { + return true + } + } + return false +} + func (r *rbac) ensureRoleBindings(ctx context.Context, sa *corev1.ServiceAccount) error { logger := logging.FromContext(ctx) @@ -495,16 +562,26 @@ func (r *rbac) updateClusterRoleBinding(ctx context.Context, rb *rbacv1.ClusterR rb.Subjects = append(rb.Subjects, subject) } + rbacClient := r.kubeClientSet.RbacV1() + hasOwnerRef := hasOwnerRefernce(rb.GetOwnerReferences(), r.ownerRef) + ownerRef := r.updateOwnerRefs(rb.GetOwnerReferences()) rb.SetOwnerReferences(ownerRef) + // If owners are different then we need to set from r.ownerRef and update the clusterRolebinding. + if !hasOwnerRef { + if _, err := rbacClient.ClusterRoleBindings().Update(ctx, rb, metav1.UpdateOptions{}); err != nil { + logger.Error(err, "failed to update "+clusterInterceptors+" crb") + return err + } + } + if hasSubject && (len(ownerRef) != 0) { logger.Info("clusterrolebinding is up to date", "action", "none") return nil } logger.Info("update existing clusterrolebinding ", clusterInterceptors) - rbacClient := r.kubeClientSet.RbacV1() if _, err := rbacClient.ClusterRoleBindings().Update(ctx, rb, metav1.UpdateOptions{}); err != nil { logger.Error(err, "failed to update "+clusterInterceptors+" crb") @@ -572,17 +649,18 @@ func (r *rbac) updateOwnerRefs(ownerRef []metav1.OwnerReference) []metav1.OwnerR return []metav1.OwnerReference{r.ownerRef} } - doUpdateOwnerRef := true - - for _, ref := range ownerRef { - if ref.APIVersion == r.ownerRef.APIVersion && ref.Kind == r.ownerRef.Kind && ref.Name == r.ownerRef.Name { - doUpdateOwnerRef = false - break + for i, ref := range ownerRef { + if ref.APIVersion != r.ownerRef.APIVersion || ref.Kind != r.ownerRef.Kind || ref.Name != r.ownerRef.Name { + // if owner reference are different remove the existing oand override with r.ownerRef + return r.removeAndUpdate(ownerRef, i) } } - if doUpdateOwnerRef { - ownerRef = append(ownerRef, r.ownerRef) - } + return ownerRef +} + +func (r *rbac) removeAndUpdate(slice []metav1.OwnerReference, s int) []metav1.OwnerReference { + ownerRef := append(slice[:s], slice[s+1:]...) + ownerRef = append(ownerRef, r.ownerRef) return ownerRef } diff --git a/third_party/github.com/hashicorp/golang-lru/simplelru/lru_interface.go b/third_party/github.com/hashicorp/golang-lru/simplelru/lru_interface.go index a0b97e3f77..92d70934d6 100644 --- a/third_party/github.com/hashicorp/golang-lru/simplelru/lru_interface.go +++ b/third_party/github.com/hashicorp/golang-lru/simplelru/lru_interface.go @@ -34,6 +34,6 @@ type LRUCache interface { // Clears all cache entries. Purge() - // Resizes cache, returning number evicted - Resize(int) int + // Resizes cache, returning number evicted + Resize(int) int }