Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ func NewConfigObserver(
NetworkLister: configInformers.Config().V1().Networks().Lister(),
ConfigMapLister: kubeInformersForOperatorNamespace.Core().V1().ConfigMaps().Lister(),
PreRunCachesSynced: []cache.InformerSynced{
operatorConfigInformers.Operator().V1().OpenShiftControllerManagers().Informer().HasSynced,
configInformers.Config().V1().Images().Informer().HasSynced,
configInformers.Config().V1().Builds().Informer().HasSynced,
configInformers.Config().V1().Networks().Informer().HasSynced,
kubeInformersForOperatorNamespace.Core().V1().ConfigMaps().Informer().HasSynced,
configInformers.Config().V1().Images().Informer().HasSynced,
configInformers.Config().V1().Builds().Informer().HasSynced,
configInformers.Config().V1().Networks().Informer().HasSynced,
kubeInformersForOperatorNamespace.Core().V1().ConfigMaps().Informer().HasSynced,
operatorConfigInformers.Operator().V1().OpenShiftControllerManagers().Informer().HasSynced,
},
},
[]factory.Informer{operatorConfigInformers.Operator().V1().OpenShiftControllerManagers().Informer()},
Expand Down
40 changes: 31 additions & 9 deletions pkg/operator/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"

configv1 "github.com/openshift/api/config/v1"
Expand All @@ -16,10 +15,14 @@ import (
operatorclient "github.com/openshift/client-go/operator/clientset/versioned"
operatorclientv1 "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1"
operatorinformers "github.com/openshift/client-go/operator/informers/externalversions"
configobservationcontroller "github.com/openshift/cluster-openshift-controller-manager-operator/pkg/operator/configobservation/configobservercontroller"
"github.com/openshift/cluster-openshift-controller-manager-operator/pkg/util"
"github.com/openshift/library-go/pkg/controller/controllercmd"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
"github.com/openshift/library-go/pkg/operator/status"
"github.com/openshift/library-go/pkg/operator/v1helpers"

configobservationcontroller "github.com/openshift/cluster-openshift-controller-manager-operator/pkg/operator/configobservation/configobservercontroller"
"github.com/openshift/cluster-openshift-controller-manager-operator/pkg/operator/usercaobservation"
"github.com/openshift/cluster-openshift-controller-manager-operator/pkg/util"
)

func RunOperator(ctx context.Context, controllerConfig *controllercmd.ControllerContext) error {
Expand All @@ -36,16 +39,15 @@ func RunOperator(ctx context.Context, controllerConfig *controllercmd.Controller
return err
}

kubeInformers := v1helpers.NewKubeInformersForNamespaces(kubeClient, util.TargetNamespace, util.OperatorNamespace, util.UserSpecifiedGlobalConfigNamespace)
operatorConfigInformers := operatorinformers.NewSharedInformerFactory(operatorClient, 10*time.Minute)
kubeInformersForOpenshiftControllerManagerNamespace := informers.NewSharedInformerFactoryWithOptions(kubeClient, 10*time.Minute, informers.WithNamespace(util.TargetNamespace))
kubeInformersForOperatorNamespace := informers.NewSharedInformerFactoryWithOptions(kubeClient, 10*time.Minute, informers.WithNamespace(util.OperatorNamespace))
configInformers := configinformers.NewSharedInformerFactory(configClient, 10*time.Minute)

operator := NewOpenShiftControllerManagerOperator(
os.Getenv("IMAGE"),
operatorConfigInformers.Operator().V1().OpenShiftControllerManagers(),
configInformers.Config().V1().Proxies(),
kubeInformersForOpenshiftControllerManagerNamespace,
kubeInformers.InformersFor(util.TargetNamespace),
operatorClient.OperatorV1(),
kubeClient,
controllerConfig.EventRecorder,
Expand All @@ -56,11 +58,30 @@ func RunOperator(ctx context.Context, controllerConfig *controllercmd.Controller
client: operatorClient.OperatorV1(),
}

// resourceSyncer synchronizes Secrets and ConfigMaps from one namespace to another.
// Bug 1826183: this will sync the proxy trustedCA ConfigMap to the
// openshift-controller-manager's user-ca ConfigMap.
resourceSyncer := resourcesynccontroller.NewResourceSyncController(
opClient,
kubeInformers,
v1helpers.CachedSecretGetter(kubeClient.CoreV1(), kubeInformers),
v1helpers.CachedConfigMapGetter(kubeClient.CoreV1(), kubeInformers),
controllerConfig.EventRecorder,
)

configObserver := configobservationcontroller.NewConfigObserver(
opClient,
operatorConfigInformers,
configInformers,
kubeInformersForOperatorNamespace,
kubeInformers.InformersFor(util.OperatorNamespace),
controllerConfig.EventRecorder,
)

// userCAObserver watches the cluster proxy config and updates the resourceSyncer.
userCAObserver := usercaobservation.NewController(
opClient,
configInformers,
resourceSyncer,
controllerConfig.EventRecorder,
)

Expand All @@ -85,12 +106,13 @@ func RunOperator(ctx context.Context, controllerConfig *controllercmd.Controller
)

operatorConfigInformers.Start(ctx.Done())
kubeInformersForOpenshiftControllerManagerNamespace.Start(ctx.Done())
kubeInformersForOperatorNamespace.Start(ctx.Done())
kubeInformers.Start(ctx.Done())
configInformers.Start(ctx.Done())

go operator.Run(ctx, 1)
go resourceSyncer.Run(ctx, 1)
go configObserver.Run(ctx, 1)
go userCAObserver.Run(ctx, 1)
go clusterOperatorStatus.Run(ctx, 1)

<-ctx.Done()
Expand Down
10 changes: 10 additions & 0 deletions pkg/operator/sync_openshiftcontrollermanager_v311_00.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ func manageOpenShiftControllerManagerConfigMap_v311_00_to_latest(kubeClient kube
resourcehash.NewObjectRef().ForConfigMap().InNamespace(util.TargetNamespace).Named("client-ca"),
resourcehash.NewObjectRef().ForSecret().InNamespace(util.TargetNamespace).Named("serving-cert"),
resourcehash.NewObjectRef().ForConfigMap().InNamespace(util.TargetNamespace).Named("openshift-global-ca"),
resourcehash.NewObjectRef().ForConfigMap().InNamespace(util.TargetNamespace).Named("openshift-user-ca"),
)
if err != nil {
return nil, false, err
Expand Down Expand Up @@ -253,6 +254,15 @@ func manageOpenShiftGlobalCAConfigMap_v311_00_to_latest(kubeClient kubernetes.In
existing, err := client.ConfigMaps(util.TargetNamespace).Get(context.TODO(), "openshift-global-ca", metav1.GetOptions{})
// Ensure we create the ConfigMap for the global CA, and that it has the right labels
// Lifted from library-go for the most part

// Bug 1826183: Build pod containers now run `update-ca-trust extract` on startup if a custom
// PKI is added to the cluster. Prior to 4.6, builds used the global CA trust bundle that was
// injected into this global-ca configmap. However, the global CA bundle is not intended to be
// used with workloads which run `update-ca-trust extract` on their own. Instead, this operator
// will directly copy the admin-provided custom PKI via the UserCAObservationController.
//
// TODO: In 4.6 we need to continue creating this ConfigMap to ensure smooth upgrades.
// In 4.7 or beyond this ConfigMap should be deleted.
if apierrors.IsNotFound(err) {
new, err := client.ConfigMaps(util.TargetNamespace).Create(context.TODO(), configMap, metav1.CreateOptions{})
if err != nil {
Expand Down
108 changes: 108 additions & 0 deletions pkg/operator/usercaobservation/user_ca_observation_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package usercaobservation

import (
"context"
"time"

configinformers "github.com/openshift/client-go/config/informers/externalversions"
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/management"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
"github.com/openshift/library-go/pkg/operator/v1helpers"
"k8s.io/apimachinery/pkg/api/errors"

"github.com/openshift/cluster-openshift-controller-manager-operator/pkg/util"
)

// Controller watches the cluster proxy config resource to see if a custom trusted CA has been
// added or removed. In the event a change is detected, this Controller makes appropriate calls to
// the provided ResourceSyncer instance.
type Controller struct {
name string
operatorConfigClient v1helpers.OperatorClient
proxyLister configlistersv1.ProxyLister
resourceSyncer resourcesynccontroller.ResourceSyncer
runFn func(ctx context.Context, workers int)
syncCtxt factory.SyncContext
}

// NewController creates a new usercaobservation.Controller instance.
func NewController(operatorConfigClient v1helpers.OperatorClient,
configInformers configinformers.SharedInformerFactory,
resourceSyncer resourcesynccontroller.ResourceSyncer,
eventRecorder events.Recorder) *Controller {
c := &Controller{
name: "UserCAObservationController",
operatorConfigClient: operatorConfigClient,
proxyLister: configInformers.Config().V1().Proxies().Lister(),
resourceSyncer: resourceSyncer,
}
informers := []factory.Informer{
operatorConfigClient.Informer(),
configInformers.Config().V1().Proxies().Informer(),
}
f := factory.New().
WithSync(c.Sync).
WithSyncContext(c.syncCtxt).
WithInformers(informers...).
ResyncEvery(10*time.Minute).
ToController(c.name, eventRecorder.WithComponentSuffix("user-ca-observation-controller"))
c.runFn = f.Run
return c
}

// Run starts the controller with the provided context and number of workers.
func (c *Controller) Run(ctx context.Context, workers int) {
c.runFn(ctx, workers)
}

// Sync runs the main synchronization logic for the controller.
func (c *Controller) Sync(ctx context.Context, syncCtx factory.SyncContext) error {
operatorSpec, _, _, err := c.operatorConfigClient.GetOperatorState()
if err != nil {
return nil
}

if !management.IsOperatorManaged(operatorSpec.ManagementState) {
return nil
}

// Bug 1826183: copy the proxy CA trust bundle to the openshift-controller-manager namespace.
// If this ConfigMap exists, the build controller will copy the contents into a ConfigMap for
// the build pod and mount it into each build pod container. The build pod containers will then
// run `update-ca-trust extract` on startup, merging the proxy CA with the default trust bundle
// provided by the openshift/builder image.
//
// If `source` returns an empty instance, the resourceSyncer will delete the destination
// ConfigMap. The build controller will expect this and mount an empty file in this situation.
source, err := c.findProxyCASource()
if err != nil {
return err
}
destination := resourcesynccontroller.ResourceLocation{
Namespace: util.TargetNamespace,
Name: "openshift-user-ca",
}
err = c.resourceSyncer.SyncConfigMap(destination, source)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so either user ca is a singleton like proxy ca, or SyncConfigMap is doing a merge of some sort

come clarification either way would be good

also, some details on why we need to copy ... i.e. the specfics around what breaks down with update-ca-trust if we read the "original" global CA vs. the user CA we've copied to would be good

How does the fact that we copy the proxy's trusted CA reconcile, get us out of jail, play nice, etc. with what I read in https://github.com/openshift/api/blob/master/config/v1/types_proxy.go#L44-L69

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the comment above to clarify what is going on overall, and why we need to copy the user CA to the openshift-controller-manager namespace. We get out of jail because build pods run update-ca-trust extract themselves.

My updated commit message indicates why we need to do this (namely, the issue with keytool formatted trust bundles).

return err
}

func (c *Controller) findProxyCASource() (resourcesynccontroller.ResourceLocation, error) {
source := resourcesynccontroller.ResourceLocation{}
proxy, err := c.proxyLister.Get("cluster")
if errors.IsNotFound(err) {
return source, nil
}
if err != nil {
return source, err
}
if len(proxy.Spec.TrustedCA.Name) > 0 {
source = resourcesynccontroller.ResourceLocation{
Namespace: util.UserSpecifiedGlobalConfigNamespace,
Name: proxy.Spec.TrustedCA.Name,
}
}
return source, nil
}
138 changes: 138 additions & 0 deletions pkg/operator/usercaobservation/user_ca_observation_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package usercaobservation

import (
"context"
"fmt"
"sync"
"testing"
"time"

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

configv1 "github.com/openshift/api/config/v1"
operatorv1 "github.com/openshift/api/operator/v1"
fakeconfig "github.com/openshift/client-go/config/clientset/versioned/fake"
configinformers "github.com/openshift/client-go/config/informers/externalversions"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"

"github.com/openshift/library-go/pkg/operator/v1helpers"

"github.com/openshift/cluster-openshift-controller-manager-operator/pkg/util"
)

type fakeSyncer struct {
configMaps map[string]string
configMapMux sync.Mutex
configMapSynced chan struct{}
configMapSyncClosed bool
}

func newFakeSyncer() *fakeSyncer {
return &fakeSyncer{
configMaps: make(map[string]string),
configMapSynced: make(chan struct{}),
}
}

func (f *fakeSyncer) SyncConfigMap(destination, source resourcesynccontroller.ResourceLocation) error {
f.configMapMux.Lock()
defer f.configMapMux.Unlock()
if f.configMapSyncClosed {
return nil
}
f.configMaps[canonicalLocation(destination)] = canonicalLocation(source)
close(f.configMapSynced)
f.configMapSyncClosed = true
return nil
}

func (f *fakeSyncer) SyncSecret(destination, source resourcesynccontroller.ResourceLocation) error {
return nil
}

func canonicalLocation(r resourcesynccontroller.ResourceLocation) string {
return fmt.Sprintf("%s/%s", r.Namespace, r.Name)
}

func TestFindProxyCASource(t *testing.T) {
destination := canonicalLocation(resourcesynccontroller.ResourceLocation{
Namespace: util.TargetNamespace,
Name: "openshift-user-ca",
})
cases := []struct {
name string
proxy *configv1.Proxy
expectedSource string
}{
{
name: "default",
proxy: &configv1.Proxy{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
},
expectedSource: "/",
},
{
name: "no found",
expectedSource: "/",
},
{
name: "has user CA",
proxy: &configv1.Proxy{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
Spec: configv1.ProxySpec{
TrustedCA: configv1.ConfigMapNameReference{
Name: "user-ca",
},
},
},
expectedSource: "openshift-config/user-ca",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
fakeOperatorClient := v1helpers.NewFakeOperatorClient(
&operatorv1.OperatorSpec{
ManagementState: operatorv1.Managed,
},
&operatorv1.OperatorStatus{},
nil,
)

configObjects := []runtime.Object{}
if tc.proxy != nil {
configObjects = append(configObjects, tc.proxy)
}
fakeConfigClient := fakeconfig.NewSimpleClientset(configObjects...)
configInformer := configinformers.NewSharedInformerFactory(fakeConfigClient, 1*time.Minute)
syncer := newFakeSyncer()
controller := NewController(fakeOperatorClient,
configInformer,
syncer,
events.NewInMemoryRecorder("test"))

ctx, ctxCancel := context.WithCancel(context.TODO())
defer ctxCancel()
go configInformer.Start(ctx.Done())
go controller.Run(ctx, 1)

select {
case <-syncer.configMapSynced:
case <-time.After(20 * time.Second):
t.Fatal("timeout waiting for configMap to by synced")
}
actualSource, ok := syncer.configMaps[destination]
if !ok {
t.Errorf("Expected source for %s, found none", destination)
}
if tc.expectedSource != actualSource {
t.Errorf("Expected source for %s to be %s, got %s", destination, tc.expectedSource, actualSource)
}
})
}
}
Loading