Skip to content

Commit

Permalink
Merge pull request #317 from p0lyn0mial/release-4.5-clean-up-oauth-ap…
Browse files Browse the repository at this point in the history
…iserver

Bug 1860922: adds OAuthAPIServer pruner controller
  • Loading branch information
openshift-merge-robot committed Aug 13, 2020
2 parents e1b4a46 + 885d005 commit 3b304ec
Show file tree
Hide file tree
Showing 72 changed files with 4,660 additions and 111 deletions.
11 changes: 6 additions & 5 deletions go.mod
Expand Up @@ -17,11 +17,12 @@ require (
go.uber.org/multierr v1.1.1-0.20180122172545-ddea229ff1df // indirect
gopkg.in/square/go-jose.v2 v2.2.2
gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.18.2
k8s.io/apimachinery v0.18.2
k8s.io/apiserver v0.18.2
k8s.io/client-go v0.18.2
k8s.io/component-base v0.18.2
k8s.io/api v0.18.6
k8s.io/apimachinery v0.18.6
k8s.io/apiserver v0.18.6
k8s.io/client-go v0.18.6
k8s.io/component-base v0.18.6
k8s.io/klog v1.0.0
k8s.io/kube-aggregator v0.18.6
monis.app/go v0.0.0-20190702030534-c65526068664
)
15 changes: 15 additions & 0 deletions go.sum
Expand Up @@ -521,24 +521,35 @@ k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ=
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8=
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE=
k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE=
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA=
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag=
k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apiserver v0.18.2 h1:fwKxdTWwwYhxvtjo0UUfX+/fsitsNtfErPNegH2x9ic=
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
k8s.io/apiserver v0.18.6 h1:HcWwcOfhj4Yv6y2igP4ZUuovyPjVLGoZcG0Tsph4Mxo=
k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg=
k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4=
k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw=
k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
k8s.io/component-base v0.18.2 h1:SJweNZAGcUvsypLGNPNGeJ9UgPZQ6+bW+gEHe8uyh/Y=
k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM=
k8s.io/component-base v0.18.6 h1:Wd6cHGwJN2qpufnirVOB3oMhyhbioGsKEi5HeDBsV+s=
k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
Expand All @@ -547,8 +558,12 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-aggregator v0.18.2 h1:mgsze91nZC27HeJi8bLRyhLINQznEUy4SOTpbOhsZEM=
k8s.io/kube-aggregator v0.18.2/go.mod h1:ijq6FnNUoKinA6kKbkN6svdTacSoQVNtKqmQ1+XJEYQ=
k8s.io/kube-aggregator v0.18.6 h1:xGP3oe0tAWEYnGWTnDPjXiIItekrnwDA2O7w0WqvGoo=
k8s.io/kube-aggregator v0.18.6/go.mod h1:MKm8inLHdeiXQJCl6UdmgMosRrqJgyxO2obTXOkey/s=
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM=
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
monis.app/go v0.0.0-20190702030534-c65526068664 h1:h9Gc+HM13BmZWiFFbSdWwlUUB/YYN5vMz0W8cN/7C1A=
Expand Down
53 changes: 53 additions & 0 deletions pkg/controller/apiservices/unmanageoauthapicontroller.go
@@ -0,0 +1,53 @@
package apiservices

import (
"context"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

operatorconfigclient "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1"
operatorclientinformers "github.com/openshift/client-go/operator/informers/externalversions"
operatorlistersv1 "github.com/openshift/client-go/operator/listers/operator/v1"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
)

// NewUnmanageAPIServicesController sets ManagingOAuthAPIServer flag to false.
// This will make OAuth APIServer (introduced in 4.6) to step down and traffic
// will be routed to the OpenShift APIServer. It's a measure required for 4.6->4.5
// downgrade.
//
// see https://github.com/openshift/enhancements/blob/master/enhancements/authentication/separate-oauth-resources.md
func NewUnmanageAPIServicesController(
name string,
authOperatorClient operatorconfigclient.AuthenticationsGetter,
authOperatorInformers operatorclientinformers.SharedInformerFactory,
eventRecorder events.Recorder,
) factory.Controller {

authInformers := authOperatorInformers.Operator().V1().Authentications()
return factory.New().
WithSync(syncUnmanageAPIServicesController(name, authOperatorClient, authInformers.Lister())).
WithInformers(authInformers.Informer()).
ToController(name, eventRecorder.WithComponentSuffix("unmanage-oauth-api-controller"))
}

func syncUnmanageAPIServicesController(controllerName string, authOperatorClient operatorconfigclient.AuthenticationsGetter, authOperatorLister operatorlistersv1.AuthenticationLister) factory.SyncFunc {
return func(ctx context.Context, syncContext factory.SyncContext) error {
operator, err := authOperatorLister.Get("cluster")
if err != nil {
return err
}

if operator.Status.ManagingOAuthAPIServer {
operatorCopy := operator.DeepCopy()
operatorCopy.Status.ManagingOAuthAPIServer = false
_, err = authOperatorClient.Authentications().UpdateStatus(ctx, operatorCopy, metav1.UpdateOptions{})
if err == nil {
syncContext.Recorder().Eventf(controllerName, "turned OAuth API managing off")
}
}

return err
}
}
94 changes: 94 additions & 0 deletions pkg/controller/apiservices/unmanageoauthapicontroller_test.go
@@ -0,0 +1,94 @@
package apiservices

import (
"context"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"

operatorv1 "github.com/openshift/api/operator/v1"
fakeoperator "github.com/openshift/client-go/operator/clientset/versioned/fake"
operatorv1listers "github.com/openshift/client-go/operator/listers/operator/v1"
"github.com/openshift/library-go/pkg/operator/events"
)

func Test_syncUnmanageAPIServicesController(t *testing.T) {
tests := []struct {
name string
operatorStatus *operatorv1.AuthenticationStatus
changed bool
expectErr bool
}{
{
name: "operator not found",
expectErr: true,
},
{
name: "managed set to true",
operatorStatus: &operatorv1.AuthenticationStatus{
ManagingOAuthAPIServer: true,
},
changed: true,
},
{
name: "managed set to false = no action",
operatorStatus: &operatorv1.AuthenticationStatus{
ManagingOAuthAPIServer: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
authOps := []runtime.Object{}
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
if tt.operatorStatus != nil {
operatorObj := &operatorv1.Authentication{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
Status: *tt.operatorStatus,
}

if err := indexer.Add(operatorObj); err != nil {
t.Fatal(err)
}
authOps = append(authOps, operatorObj)
}
fakeOperatorClient := fakeoperator.NewSimpleClientset(authOps...)
operatorLister := operatorv1listers.NewAuthenticationLister(indexer)

testRecorder := events.NewInMemoryRecorder("test")
if gotErr := syncUnmanageAPIServicesController("testUnmanagedController", fakeOperatorClient.OperatorV1(), operatorLister)(context.TODO(), testSyncContext{recorder: testRecorder}); tt.expectErr != (gotErr != nil) {
t.Errorf("syncUnmanageAPIServicesController() => expected error: %v, but got %v", tt.expectErr, gotErr)
}

var updateObserved bool
for _, a := range fakeOperatorClient.Actions() {
if a.GetVerb() == "update" {
updateObserved = true
break
}
}
if !tt.changed {
if len(testRecorder.Events()) > 0 || updateObserved {
t.Errorf("expected the operator status to be the same, but that did not happen; update observed: %v; events: %v", updateObserved, testRecorder.Events())
}
} else if len(testRecorder.Events()) == 0 || !updateObserved {
t.Errorf("expected change of the operator status:, but that did not happen; update observed: %v; events: %v", updateObserved, testRecorder.Events())
}
})
}
}

type testSyncContext struct {
recorder events.Recorder
}

func (c testSyncContext) Recorder() events.Recorder { return c.recorder }

func (c testSyncContext) Queue() workqueue.RateLimitingInterface { return nil }

func (c testSyncContext) QueueKey() string { return "testkey" }
137 changes: 137 additions & 0 deletions pkg/controller/oauthapiserverpruner/oauth_apiserver_pruner.go
@@ -0,0 +1,137 @@
package oauthapiserverpruner

import (
"context"

"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/klog"
apiregistrationv1informer "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1"
apiregistrationv1lister "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1"

operatorclientinformers "github.com/openshift/client-go/operator/informers/externalversions"
operatorlistersv1 "github.com/openshift/client-go/operator/listers/operator/v1"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/encryption/secrets"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/v1helpers"
)

const (
OAuthApiServerNsToRemove = "openshift-oauth-apiserver"
)

type oauthAPIServerPrunerController struct {
apiServicesManagedByOAS []string

namespaceClient corev1client.NamespaceInterface
secretClient corev1client.SecretInterface

apiregistrationv1Lister apiregistrationv1lister.APIServiceLister
authOperatorLister operatorlistersv1.AuthenticationLister
namespaceLister corev1listers.NamespaceLister
}

// NewOAuthAPIServerPrunerController removes "openshift-oauth-apiserver" namespace.
//
// The namespace holds the OAuth API Server and all necessary resources that are valid only for 4.6+ clusters
// This controller is intended to clean up in case of a downgrade from 4.6 to 4.5.
func NewOAuthAPIServerPrunerController(
name string,
apiServicesManagedByOAS []string,
kubeClient corev1client.CoreV1Interface,
kubeInformersForNamespaces v1helpers.KubeInformersForNamespaces,
authOperatorInformers operatorclientinformers.SharedInformerFactory,
apiregistrationv1Informer apiregistrationv1informer.APIServiceInformer,
eventRecorder events.Recorder) factory.Controller {

authInformers := authOperatorInformers.Operator().V1().Authentications()

c := &oauthAPIServerPrunerController{
apiServicesManagedByOAS: apiServicesManagedByOAS,
namespaceClient: kubeClient.Namespaces(),
secretClient: kubeClient.Secrets(OAuthApiServerNsToRemove),
apiregistrationv1Lister: apiregistrationv1Informer.Lister(),
authOperatorLister: authInformers.Lister(),
namespaceLister: kubeInformersForNamespaces.InformersFor("").Core().V1().Namespaces().Lister(),
}

controllerFactory := factory.New()
controllerFactory.WithInformers(authInformers.Informer(), kubeInformersForNamespaces.InformersFor("").Core().V1().Namespaces().Informer(), apiregistrationv1Informer.Informer())
controllerFactory.WithSync(c.sync)

return controllerFactory.ToController(name, eventRecorder.WithComponentSuffix("oauth-apiserver-cleaner-controller"))
}

func (c *oauthAPIServerPrunerController) sync(ctx context.Context, _ factory.SyncContext) error {
// check the ManagingOAuthAPIServer field
operator, err := c.authOperatorLister.Get("cluster")
if err != nil {
return err
}

if operator.Status.ManagingOAuthAPIServer {
klog.V(2).Info("waiting for ManagingOAuthAPIServer field to be set to false")
return nil // we will be called again once the operator status changes
}

// be graceful and check if the API Services that were managed by CAO in 4.6 are now being managed by OAS-O
for _, apiServiceName := range c.apiServicesManagedByOAS {
managedByOAS, err := c.isAPIServiceMangedByOAS(apiServiceName)
if err != nil {
return err
}
if !managedByOAS {
klog.V(2).Infof("waiting for the api service %s to be managed by OAS-O", apiServiceName)
return nil // we will be called again once the api services change
}
}

// remove the namespace
if _, err := c.namespaceLister.Get(OAuthApiServerNsToRemove); err != nil {
if errors.IsNotFound(err) {
return nil // no-op the namespace was already removed
}
return err
}
if err := c.namespaceClient.Delete(ctx, OAuthApiServerNsToRemove, metav1.DeleteOptions{}); err != nil {
return err
}

// remove the finalizer from the encryption config secrets (encryption-config ,encryption-config-REVISION)
// otherwise the namespace won't be removed
allSecrets, err := c.secretClient.List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var finalizerDeletionErrs []error
for _, secret := range allSecrets.Items {
if finalizers := sets.NewString(secret.Finalizers...); finalizers.Has(secrets.EncryptionSecretFinalizer) {
delete(finalizers, secrets.EncryptionSecretFinalizer)
secret.Finalizers = finalizers.List()
if _, err := c.secretClient.Update(ctx, &secret, metav1.UpdateOptions{}); err != nil {
finalizerDeletionErrs = append(finalizerDeletionErrs, err)
}
}
}
return utilerrors.NewAggregate(finalizerDeletionErrs)
}

func (c *oauthAPIServerPrunerController) isAPIServiceMangedByOAS(apiServiceName string) (bool, error) {
existingApiService, err := c.apiregistrationv1Lister.Get(apiServiceName)
if err != nil {
return false, err
}

// we don't check the "authentication.operator.openshift.io/managed" annotation because it is only set by CAO (4.6) to true.
// there is no component that sets it back to false or removes it
if existingApiService.Spec.Service != nil && existingApiService.Spec.Service.Namespace != OAuthApiServerNsToRemove {
return true, nil
}

return false, nil
}

0 comments on commit 3b304ec

Please sign in to comment.