-
Notifications
You must be signed in to change notification settings - Fork 546
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1049 from njhale/clean-orefs-4.1
[release-4.1] Bug 1755646: Cleanup leftover cross-namespace OwnerReferences
- Loading branch information
Showing
6 changed files
with
794 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package main | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
rbacv1 "k8s.io/api/rbac/v1" | ||
"k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/apimachinery/pkg/util/wait" | ||
"k8s.io/client-go/util/retry" | ||
|
||
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" | ||
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" | ||
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" | ||
) | ||
|
||
const ( | ||
pollInterval = 1 * time.Second | ||
pollDuration = 5 * time.Minute | ||
) | ||
|
||
type checkResourceFunc func() error | ||
type deleteResourceFunc func() error | ||
type mutateMeta func(obj metav1.Object) (mutated bool) | ||
|
||
func cleanup(logger *logrus.Logger, c operatorclient.ClientInterface, crc versioned.Interface) { | ||
if err := cleanupOwnerReferences(c, crc); err != nil { | ||
logger.WithError(err).Fatal("couldn't cleanup cross-namespace ownerreferences") | ||
} | ||
|
||
if err := waitForDelete(checkSubscription(crc, "packageserver"), deleteSubscription(crc, "packageserver")); err != nil { | ||
logger.WithError(err).Fatal("couldn't clean previous release") | ||
} | ||
} | ||
|
||
func waitForDelete(checkResource checkResourceFunc, deleteResource deleteResourceFunc) error { | ||
if err := checkResource(); err != nil && errors.IsNotFound(err) { | ||
return nil | ||
} | ||
if err := deleteResource(); err != nil { | ||
return err | ||
} | ||
err := wait.Poll(pollInterval, pollDuration, func() (bool, error) { | ||
err := checkResource() | ||
if errors.IsNotFound(err) { | ||
return true, nil | ||
} | ||
if err != nil { | ||
return false, err | ||
} | ||
return false, nil | ||
}) | ||
|
||
return err | ||
} | ||
|
||
func checkSubscription(crc versioned.Interface, name string) checkResourceFunc { | ||
return func() error { | ||
_, err := crc.OperatorsV1alpha1().Subscriptions(*namespace).Get(name, metav1.GetOptions{}) | ||
return err | ||
} | ||
} | ||
|
||
func deleteSubscription(crc versioned.Interface, name string) deleteResourceFunc { | ||
return func() error { | ||
return crc.OperatorsV1alpha1().Subscriptions(*namespace).Delete(name, metav1.NewDeleteOptions(0)) | ||
} | ||
} | ||
|
||
// cleanupOwnerReferences cleans up inter-namespace and cluster-to-namespace scoped OwnerReferences to ClusterServiceVersions. | ||
// | ||
// Cross-namespace and cluster-to-namespace scoped OwnerReferences may cause sibling resources in the owner namespace to be | ||
// deleted sporadically (see CVE-2019-3884 https://access.redhat.com/security/cve/cve-2019-3884). Older versions of OLM use both types of | ||
// OwnerReference, and in cases where OLM is updated, they must be removed to prevent erroneous deletion of OLM's self-hosted components. | ||
func cleanupOwnerReferences(c operatorclient.ClientInterface, crc versioned.Interface) error { | ||
listOpts := metav1.ListOptions{} | ||
csvs, err := crc.OperatorsV1alpha1().ClusterServiceVersions(metav1.NamespaceAll).List(listOpts) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
uidNamespaces := map[types.UID]string{} | ||
for _, csv := range csvs.Items { | ||
uidNamespaces[csv.GetUID()] = csv.GetNamespace() | ||
} | ||
removeBadRefs := crossNamespaceOwnerReferenceRemoval(v1alpha1.ClusterServiceVersionKind, uidNamespaces) | ||
|
||
// Cleanup cross-namespace OwnerReferences on CSVs, ClusterRoles/Bindings, and Roles/Bindings | ||
var objs []metav1.Object | ||
for _, obj := range csvs.Items { | ||
objs = append(objs, &obj) | ||
} | ||
|
||
clusterRoles, _ := c.KubernetesInterface().RbacV1().ClusterRoles().List(listOpts) | ||
for _, obj := range clusterRoles.Items { | ||
objs = append(objs, &obj) | ||
} | ||
|
||
clusterRoleBindings, _ := c.KubernetesInterface().RbacV1().ClusterRoleBindings().List(listOpts) | ||
for _, obj := range clusterRoleBindings.Items { | ||
objs = append(objs, &obj) | ||
} | ||
|
||
roles, _ := c.KubernetesInterface().RbacV1().Roles(metav1.NamespaceAll).List(listOpts) | ||
for _, obj := range roles.Items { | ||
objs = append(objs, &obj) | ||
} | ||
roleBindings, _ := c.KubernetesInterface().RbacV1().RoleBindings(metav1.NamespaceAll).List(listOpts) | ||
for _, obj := range roleBindings.Items { | ||
objs = append(objs, &obj) | ||
} | ||
|
||
for _, obj := range objs { | ||
if !removeBadRefs(obj) { | ||
continue | ||
} | ||
|
||
update := func() error { | ||
// If this is not a type we care about, do nothing | ||
return nil | ||
} | ||
switch v := obj.(type) { | ||
case *v1alpha1.ClusterServiceVersion: | ||
update = func() error { | ||
_, err := crc.OperatorsV1alpha1().ClusterServiceVersions(v.GetNamespace()).Update(v) | ||
return err | ||
} | ||
case *rbacv1.ClusterRole: | ||
update = func() error { | ||
_, err = c.KubernetesInterface().RbacV1().ClusterRoles().Update(v) | ||
return err | ||
} | ||
case *rbacv1.ClusterRoleBinding: | ||
update = func() error { | ||
_, err = c.KubernetesInterface().RbacV1().ClusterRoleBindings().Update(v) | ||
return err | ||
} | ||
case *rbacv1.Role: | ||
update = func() error { | ||
_, err = c.KubernetesInterface().RbacV1().Roles(v.GetNamespace()).Update(v) | ||
return err | ||
} | ||
case *rbacv1.RoleBinding: | ||
update = func() error { | ||
_, err = c.KubernetesInterface().RbacV1().RoleBindings(v.GetNamespace()).Update(v) | ||
return err | ||
} | ||
} | ||
|
||
if err := retry.RetryOnConflict(retry.DefaultBackoff, update); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func crossNamespaceOwnerReferenceRemoval(kind string, uidNamespaces map[types.UID]string) mutateMeta { | ||
return func(obj metav1.Object) (mutated bool) { | ||
var cleanRefs []metav1.OwnerReference | ||
objNamespace := obj.GetNamespace() | ||
for _, ref := range obj.GetOwnerReferences() { | ||
if ref.Kind == kind { | ||
refNamespace, ok := uidNamespaces[ref.UID] | ||
if !ok || (refNamespace != metav1.NamespaceAll && refNamespace != objNamespace) { | ||
mutated = true | ||
continue | ||
} | ||
} | ||
|
||
cleanRefs = append(cleanRefs, ref) | ||
} | ||
|
||
if mutated { | ||
obj.SetOwnerReferences(cleanRefs) | ||
} | ||
|
||
return | ||
} | ||
} |
Oops, something went wrong.