diff --git a/pkg/client/fake/fake_client.go b/pkg/client/fake/fake_client.go index fdad7d229d..876eba57ab 100644 --- a/pkg/client/fake/fake_client.go +++ b/pkg/client/fake/fake_client.go @@ -100,10 +100,11 @@ func NewFakeClient(objs ...crclient.Object) cnoclient.Client { } } co := &configv1.ClusterOperator{ObjectMeta: metav1.ObjectMeta{Name: ""}} + proxy := &configv1.Proxy{ObjectMeta: metav1.ObjectMeta{Name: ""}} fc := FakeClusterClient{ kClient: faketyped.NewClientset(ooTyped...), dynclient: fakedynamic.NewSimpleDynamicClient(scheme.Scheme, oo...), - crclient: crfake.NewClientBuilder().WithStatusSubresource(co).WithObjects(objs...).Build(), + crclient: crfake.NewClientBuilder().WithStatusSubresource(co, proxy).WithObjects(objs...).Build(), osOperClient: osoperfakeclient.NewClientset(), } return &FakeClient{ diff --git a/pkg/controller/proxyconfig/controller.go b/pkg/controller/proxyconfig/controller.go index fbc9b8ecb9..05b9475d8f 100644 --- a/pkg/controller/proxyconfig/controller.go +++ b/pkg/controller/proxyconfig/controller.go @@ -79,9 +79,24 @@ func add(mgr manager.Manager, r *ReconcileProxyConfig) error { return err } + // Network and Infrastructure contribute to Proxy status. Map changes to the + // canonical Proxy request because all three resources are cluster-scoped and + // named "cluster". + proxyEventHandler := handler.EnqueueRequestsFromMapFunc(enqueueProxy) + if err := c.Watch(source.Kind[crclient.Object](mgr.GetCache(), &configv1.Network{}, proxyEventHandler)); err != nil { + return err + } + if err := c.Watch(source.Kind[crclient.Object](mgr.GetCache(), &configv1.Infrastructure{}, proxyEventHandler)); err != nil { + return err + } + return nil } +func enqueueProxy(_ context.Context, _ crclient.Object) []reconcile.Request { + return []reconcile.Request{{NamespacedName: names.Proxy()}} +} + // ReconcileProxyConfig reconciles a Proxy object type ReconcileProxyConfig struct { // This client, initialized using mgr.GetClient() above, is a split client @@ -97,221 +112,21 @@ type ReconcileProxyConfig struct { // and will ensure either object is in the desired state. func (r *ReconcileProxyConfig) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { defer utilruntime.HandleCrash(r.status.SetDegradedOnPanicAndCrash) - validate := true trustBundle := &corev1.ConfigMap{} switch { case request.NamespacedName == names.Proxy(): var err error - proxyConfig := &configv1.Proxy{} - infraConfig := &configv1.Infrastructure{} - netConfig := &configv1.Network{} - clusterConfig := &corev1.ConfigMap{} - - log.Printf("Reconciling proxy '%s'", request.Name) - if err := r.client.Get(ctx, request.NamespacedName, proxyConfig); err != nil { - if apierrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Return and don't requeue - log.Println("proxy not found; reconciliation will be skipped", "request", request) - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, fmt.Errorf("failed to get proxy '%s': %v", request.Name, err) - } - - // A nil proxy is generated by upgrades and installs not requiring a proxy. - if !isSpecHTTPProxySet(&proxyConfig.Spec) && - !isSpecHTTPSProxySet(&proxyConfig.Spec) && - !isSpecNoProxySet(&proxyConfig.Spec) { - log.Printf("httpProxy, httpsProxy and noProxy not defined for proxy '%s'; validation will be skipped", - request.Name) - validate = false - } - - if validate { - if err := r.ValidateProxyConfig(&proxyConfig.Spec); err != nil { - log.Printf("Failed to validate proxy '%s': %v", proxyConfig.Name, err) - r.status.SetDegraded(statusmanager.ProxyConfig, "InvalidProxyConfig", - fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ - "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxyConfig.Name, err, proxyConfig.Name)) - return reconcile.Result{}, fmt.Errorf("failed to validate proxy '%s': %v", proxyConfig.Name, err) - } + trustBundle, err = r.reconcileProxy(ctx, request) + if err != nil || trustBundle == nil { + return reconcile.Result{}, err } - - if !isSpecTrustedCASet(&proxyConfig.Spec) { - // Create a configmap containing the system trust bundle. - if trustBundle, err = r.generateSystemTrustBundle(); err != nil { - log.Printf("Failed to generate system trust bundle configmap '%s/%s': %v", - names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err) - r.status.SetDegraded(statusmanager.ProxyConfig, "GenerateConfigMapFailure", - fmt.Sprintf("failed to generate system trust bundle configmap '%s/%s (%v).", - names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err)) - return reconcile.Result{}, fmt.Errorf("failed to generate system trust bundle configmap '%s/%s': %v", - names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err) - } - } else { - // Validate trustedCA of proxy spec. - proxyData, systemData, err := r.validateTrustedCA(proxyConfig.Spec.TrustedCA.Name) - if err != nil { - log.Printf("Failed to validate trustedCA for proxy '%s': %v", proxyConfig.Name, err) - r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "InvalidProxyConfig", - fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ - "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxyConfig.Name, err, proxyConfig.Name)) - return reconcile.Result{}, fmt.Errorf("failed to validate trustedCA for proxy '%s': %v", - proxyConfig.Name, err) - } - - // Create a configmap containing the merged proxy.trustedCA/system bundles. - trustBundle, err = r.mergeTrustBundlesToConfigMap(proxyData, systemData) - if err != nil { - log.Printf("Failed to merge trustedCA and system bundles for proxy '%s': %v", proxyConfig.Name, err) - r.status.SetDegraded(statusmanager.ProxyConfig, "ProxyCAMergeFailure", - fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ - "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxyConfig.Name, err, proxyConfig.Name)) - return reconcile.Result{}, fmt.Errorf("failed to merge trustedCA and system bundles for proxy '%s': %v", - proxyConfig.Name, err) - } - } - - // Only proceed if the required config objects can be collected. - if err := r.client.Get(ctx, types.NamespacedName{Name: names.CLUSTER_CONFIG}, infraConfig); err != nil { - log.Printf("Failed to get infrastructure config '%s': %v", names.CLUSTER_CONFIG, err) - r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "InfraConfigError", - fmt.Sprintf("Error getting infrastructure config %s: %v", names.CLUSTER_CONFIG, err)) - return reconcile.Result{}, fmt.Errorf("failed to get infrastructure config '%s': %v", names.CLUSTER_CONFIG, err) - } - if err := r.client.Get(ctx, types.NamespacedName{Name: names.CLUSTER_CONFIG}, netConfig); err != nil { - log.Printf("Failed to get network config '%s': %v", names.CLUSTER_CONFIG, err) - r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "NetworkConfigError", - fmt.Sprintf("Error getting network config '%s': %v.", names.CLUSTER_CONFIG, err)) - return reconcile.Result{}, fmt.Errorf("failed to get network config '%s': %v", names.CLUSTER_CONFIG, err) - } - if err := r.client.Get(ctx, types.NamespacedName{Name: "cluster-config-v1", Namespace: "kube-system"}, - clusterConfig); err != nil { - log.Printf("Failed to get configmap '%s/%s': %v", clusterConfig.Namespace, clusterConfig.Name, err) - r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "ClusterConfigError", - fmt.Sprintf("Error getting cluster config configmap '%s/%s': %v.", clusterConfig.Namespace, - clusterConfig.Name, err)) - return reconcile.Result{}, fmt.Errorf("failed to get configmap '%s/%s': %v", clusterConfig.Namespace, clusterConfig.Name, err) - } - // Update proxy status. - if err := r.syncProxyStatus(proxyConfig, infraConfig, netConfig, clusterConfig); err != nil { - log.Printf("Could not sync proxy '%s' status: %v", proxyConfig.Name, err) - r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "StatusError", - fmt.Sprintf("Could not update proxy '%s' status: %v", proxyConfig.Name, err)) - return reconcile.Result{}, fmt.Errorf("failed to sync proxy '%s': %v", names.PROXY_CONFIG, err) - } - log.Printf("Reconciling proxy '%s' complete", request.Name) case request.Namespace == names.ADDL_TRUST_BUNDLE_CONFIGMAP_NS: - log.Printf("Reconciling additional trust bundle configmap '%s/%s'", request.Namespace, request.Name) - if err := r.client.Get(ctx, request.NamespacedName, trustBundle); err != nil { - if apierrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Return and don't requeue - log.Println("configmap not found; reconciliation will be skipped", "request", request) - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, fmt.Errorf("failed to get configmap '%s': %v", request, err) - } - - // Only proceed if request matches the configmap referenced by proxy trustedCA. - if err := r.configMapIsProxyTrustedCA(trustBundle.Name); err != nil { - log.Printf("configmap '%s/%s' name differs from trustedCA of proxy '%s' or trustedCA not set; "+ - "reconciliation will be skipped", trustBundle.Namespace, trustBundle.Name, names.PROXY_CONFIG) - return reconcile.Result{}, nil - } - - // Validate the trust bundle configmap. - proxyData, systemData, err := r.validateTrustedCA(trustBundle.Name) - if err != nil { - log.Printf("Failed to validate additional trust bundle configmap '%s/%s': %v", trustBundle.Namespace, - trustBundle.Name, err) - r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "TrustBundleValidationFailure", - fmt.Sprintf("Failed to validate additional trust bundle configmap '%s/%s' (%v)", - trustBundle.Namespace, trustBundle.Name, err)) - return reconcile.Result{}, fmt.Errorf("failed to validate additional trust bundle configmap '%s/%s': %v", - trustBundle.Namespace, trustBundle.Name, err) - } - - // Create a configmap containing the merged proxy.trustedCA/system bundles. - _, err = r.mergeTrustBundlesToConfigMap(proxyData, systemData) - if err != nil { - log.Printf("Failed to merge trustedCA and system bundles for proxy '%s': %v", names.PROXY_CONFIG, err) - r.status.SetDegraded(statusmanager.ProxyConfig, "EnsureProxyConfigFailure", - fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ - "Use 'oc edit proxy.config.openshift.io %s' to fix.", names.PROXY_CONFIG, err, names.PROXY_CONFIG)) - return reconcile.Result{}, fmt.Errorf("failed to merge trustedCA and system bundles for proxy '%s': %v", - names.PROXY_CONFIG, err) - } - - proxyConfig := &configv1.Proxy{} - if err := r.client.Get(ctx, names.Proxy(), proxyConfig); err != nil { - if apierrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Return and don't requeue - log.Println("proxy not found; reconciliation will be skipped", "request", request) - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, fmt.Errorf("failed to get proxy '%s': %v", request.Name, err) - } - - // A nil proxy is generated by upgrades and installs not requiring a proxy. - if !isSpecHTTPProxySet(&proxyConfig.Spec) && - !isSpecHTTPSProxySet(&proxyConfig.Spec) && - !isSpecNoProxySet(&proxyConfig.Spec) { - log.Printf("httpProxy, httpsProxy and noProxy not defined for proxy '%s'; validation will be skipped", - request.Name) - validate = false - } - - if validate { - if err := r.ValidateProxyConfig(&proxyConfig.Spec); err != nil { - log.Printf("Failed to validate proxy '%s': %v", proxyConfig.Name, err) - r.status.SetDegraded(statusmanager.ProxyConfig, "InvalidProxyConfig", - fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ - "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxyConfig.Name, err, proxyConfig.Name)) - return reconcile.Result{}, fmt.Errorf("failed to validate proxy '%s': %v", proxyConfig.Name, err) - } - } - - if !isSpecTrustedCASet(&proxyConfig.Spec) { - // Create a configmap containing the system trust bundle. - if trustBundle, err = r.generateSystemTrustBundle(); err != nil { - log.Printf("Failed to generate system trust bundle configmap '%s/%s': %v", - names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err) - r.status.SetDegraded(statusmanager.ProxyConfig, "GenerateConfigMapFailure", - fmt.Sprintf("failed to generate system trust bundle configmap '%s/%s (%v).", - names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err)) - return reconcile.Result{}, fmt.Errorf("failed to generate system trust bundle configmap '%s/%s': %v", - names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err) - } - } else { - // Validate trustedCA of proxy spec. - proxyData, systemData, err := r.validateTrustedCA(proxyConfig.Spec.TrustedCA.Name) - if err != nil { - log.Printf("Failed to validate trustedCA for proxy '%s': %v", proxyConfig.Name, err) - r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "InvalidProxyConfig", - fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ - "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxyConfig.Name, err, proxyConfig.Name)) - return reconcile.Result{}, fmt.Errorf("failed to validate trustedCA for proxy '%s': %v", - proxyConfig.Name, err) - } - // Create a configmap containing the merged proxy.trustedCA/system bundles. - trustBundle, err = r.mergeTrustBundlesToConfigMap(proxyData, systemData) - if err != nil { - log.Printf("Failed to merge trustedCA and system bundles for proxy '%s': %v", proxyConfig.Name, err) - r.status.SetDegraded(statusmanager.ProxyConfig, "ProxyCAMergeFailure", - fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ - "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxyConfig.Name, err, proxyConfig.Name)) - return reconcile.Result{}, fmt.Errorf("failed to merge trustedCA and system bundles for proxy '%s': %v", - proxyConfig.Name, err) - } + var err error + trustBundle, err = r.reconcileAdditionalTrustBundle(ctx, request) + if err != nil || trustBundle == nil { + return reconcile.Result{}, err } - - log.Printf("Reconciling additional trust bundle configmap '%s/%s' complete", request.Namespace, request.Name) default: // unknown object log.Println("Ignoring unknown object, reconciliation will be skipped", "request", request) @@ -334,6 +149,186 @@ func (r *ReconcileProxyConfig) Reconcile(ctx context.Context, request reconcile. return reconcile.Result{}, nil } +func (r *ReconcileProxyConfig) reconcileProxy(ctx context.Context, request reconcile.Request) (*corev1.ConfigMap, error) { + proxy := &configv1.Proxy{} + infrastructure := &configv1.Infrastructure{} + network := &configv1.Network{} + clusterConfigMap := &corev1.ConfigMap{} + + log.Printf("Reconciling proxy '%s'", request.Name) + if err := r.client.Get(ctx, request.NamespacedName, proxy); err != nil { + if apierrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Return and don't requeue + log.Println("proxy not found; reconciliation will be skipped", "request", request) + return nil, nil + } + // Error reading the object - requeue the request. + return nil, fmt.Errorf("failed to get proxy '%s': %v", request.Name, err) + } + + trustBundle, err := r.desiredTrustBundle(proxy, request.Name) + if err != nil { + return nil, err + } + + // Only proceed if the required config objects can be collected. + if err := r.client.Get(ctx, types.NamespacedName{Name: names.CLUSTER_CONFIG}, infrastructure); err != nil { + log.Printf("Failed to get infrastructure config '%s': %v", names.CLUSTER_CONFIG, err) + r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "InfraConfigError", + fmt.Sprintf("Error getting infrastructure config %s: %v", names.CLUSTER_CONFIG, err)) + return nil, fmt.Errorf("failed to get infrastructure config '%s': %v", names.CLUSTER_CONFIG, err) + } + if err := r.client.Get(ctx, types.NamespacedName{Name: names.CLUSTER_CONFIG}, network); err != nil { + log.Printf("Failed to get network config '%s': %v", names.CLUSTER_CONFIG, err) + r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "NetworkConfigError", + fmt.Sprintf("Error getting network config '%s': %v.", names.CLUSTER_CONFIG, err)) + return nil, fmt.Errorf("failed to get network config '%s': %v", names.CLUSTER_CONFIG, err) + } + if err := r.client.Get(ctx, types.NamespacedName{Name: "cluster-config-v1", Namespace: "kube-system"}, + clusterConfigMap); err != nil { + log.Printf("Failed to get configmap '%s/%s': %v", clusterConfigMap.Namespace, clusterConfigMap.Name, err) + r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "ClusterConfigError", + fmt.Sprintf("Error getting cluster config configmap '%s/%s': %v.", clusterConfigMap.Namespace, + clusterConfigMap.Name, err)) + return nil, fmt.Errorf("failed to get configmap '%s/%s': %v", clusterConfigMap.Namespace, clusterConfigMap.Name, err) + } + // Update proxy status. + if err := r.syncProxyStatus(proxy, infrastructure, network, clusterConfigMap); err != nil { + log.Printf("Could not sync proxy '%s' status: %v", proxy.Name, err) + r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "StatusError", + fmt.Sprintf("Could not update proxy '%s' status: %v", proxy.Name, err)) + return nil, fmt.Errorf("failed to sync proxy '%s': %v", names.PROXY_CONFIG, err) + } + log.Printf("Reconciling proxy '%s' complete", request.Name) + + return trustBundle, nil +} + +func (r *ReconcileProxyConfig) reconcileAdditionalTrustBundle(ctx context.Context, request reconcile.Request) (*corev1.ConfigMap, error) { + trustBundle := &corev1.ConfigMap{} + + log.Printf("Reconciling additional trust bundle configmap '%s/%s'", request.Namespace, request.Name) + if err := r.client.Get(ctx, request.NamespacedName, trustBundle); err != nil { + if apierrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Return and don't requeue + log.Println("configmap not found; reconciliation will be skipped", "request", request) + return nil, nil + } + // Error reading the object - requeue the request. + return nil, fmt.Errorf("failed to get configmap '%s': %v", request, err) + } + + // Only proceed if request matches the configmap referenced by proxy trustedCA. + if err := r.configMapIsProxyTrustedCA(trustBundle.Name); err != nil { + log.Printf("configmap '%s/%s' name differs from trustedCA of proxy '%s' or trustedCA not set; "+ + "reconciliation will be skipped", trustBundle.Namespace, trustBundle.Name, names.PROXY_CONFIG) + return nil, nil + } + + // Validate the trust bundle configmap. + proxyData, systemData, err := r.validateTrustedCA(trustBundle.Name) + if err != nil { + log.Printf("Failed to validate additional trust bundle configmap '%s/%s': %v", trustBundle.Namespace, + trustBundle.Name, err) + r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "TrustBundleValidationFailure", + fmt.Sprintf("Failed to validate additional trust bundle configmap '%s/%s' (%v)", + trustBundle.Namespace, trustBundle.Name, err)) + return nil, fmt.Errorf("failed to validate additional trust bundle configmap '%s/%s': %v", + trustBundle.Namespace, trustBundle.Name, err) + } + + // Create a configmap containing the merged proxy.trustedCA/system bundles. + _, err = r.mergeTrustBundlesToConfigMap(proxyData, systemData) + if err != nil { + log.Printf("Failed to merge trustedCA and system bundles for proxy '%s': %v", names.PROXY_CONFIG, err) + r.status.SetDegraded(statusmanager.ProxyConfig, "EnsureProxyConfigFailure", + fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ + "Use 'oc edit proxy.config.openshift.io %s' to fix.", names.PROXY_CONFIG, err, names.PROXY_CONFIG)) + return nil, fmt.Errorf("failed to merge trustedCA and system bundles for proxy '%s': %v", + names.PROXY_CONFIG, err) + } + + proxy := &configv1.Proxy{} + if err := r.client.Get(ctx, names.Proxy(), proxy); err != nil { + if apierrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Return and don't requeue + log.Println("proxy not found; reconciliation will be skipped", "request", request) + return nil, nil + } + // Error reading the object - requeue the request. + return nil, fmt.Errorf("failed to get proxy '%s': %v", request.Name, err) + } + + trustBundle, err = r.desiredTrustBundle(proxy, request.Name) + if err != nil { + return nil, err + } + + log.Printf("Reconciling additional trust bundle configmap '%s/%s' complete", request.Namespace, request.Name) + + return trustBundle, nil +} + +// desiredTrustBundle validates the proxy configuration and builds the managed +// trust bundle ConfigMap that should be synced to the API server. +func (r *ReconcileProxyConfig) desiredTrustBundle(proxy *configv1.Proxy, requestName string) (*corev1.ConfigMap, error) { + // A nil proxy is generated by upgrades and installs not requiring a proxy. + if !isSpecHTTPProxySet(&proxy.Spec) && + !isSpecHTTPSProxySet(&proxy.Spec) && + !isSpecNoProxySet(&proxy.Spec) { + log.Printf("httpProxy, httpsProxy and noProxy not defined for proxy '%s'; validation will be skipped", + requestName) + } else if err := r.ValidateProxyConfig(&proxy.Spec); err != nil { + log.Printf("Failed to validate proxy '%s': %v", proxy.Name, err) + r.status.SetDegraded(statusmanager.ProxyConfig, "InvalidProxyConfig", + fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ + "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxy.Name, err, proxy.Name)) + return nil, fmt.Errorf("failed to validate proxy '%s': %v", proxy.Name, err) + } + + if !isSpecTrustedCASet(&proxy.Spec) { + // Create a configmap containing the system trust bundle. + trustBundle, err := r.generateSystemTrustBundle() + if err != nil { + log.Printf("Failed to generate system trust bundle configmap '%s/%s': %v", + names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err) + r.status.SetDegraded(statusmanager.ProxyConfig, "GenerateConfigMapFailure", + fmt.Sprintf("failed to generate system trust bundle configmap '%s/%s (%v).", + names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err)) + return nil, fmt.Errorf("failed to generate system trust bundle configmap '%s/%s': %v", + names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS, names.TRUSTED_CA_BUNDLE_CONFIGMAP, err) + } + return trustBundle, nil + } + + // Validate trustedCA of proxy spec. + proxyData, systemData, err := r.validateTrustedCA(proxy.Spec.TrustedCA.Name) + if err != nil { + log.Printf("Failed to validate trustedCA for proxy '%s': %v", proxy.Name, err) + r.status.MaybeSetDegraded(statusmanager.ProxyConfig, "InvalidProxyConfig", + fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ + "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxy.Name, err, proxy.Name)) + return nil, fmt.Errorf("failed to validate trustedCA for proxy '%s': %v", + proxy.Name, err) + } + + // Create a configmap containing the merged proxy.trustedCA/system bundles. + trustBundle, err := r.mergeTrustBundlesToConfigMap(proxyData, systemData) + if err != nil { + log.Printf("Failed to merge trustedCA and system bundles for proxy '%s': %v", proxy.Name, err) + r.status.SetDegraded(statusmanager.ProxyConfig, "ProxyCAMergeFailure", + fmt.Sprintf("The configuration is invalid for proxy '%s' (%v). "+ + "Use 'oc edit proxy.config.openshift.io %s' to fix.", proxy.Name, err, proxy.Name)) + return nil, fmt.Errorf("failed to merge trustedCA and system bundles for proxy '%s': %v", + proxy.Name, err) + } + + return trustBundle, nil +} + // isSpecHTTPProxySet returns true if spec.httpProxy of // proxyConfig is set. func isSpecHTTPProxySet(proxyConfig *configv1.ProxySpec) bool { diff --git a/pkg/controller/proxyconfig/controller_test.go b/pkg/controller/proxyconfig/controller_test.go new file mode 100644 index 0000000000..3030164619 --- /dev/null +++ b/pkg/controller/proxyconfig/controller_test.go @@ -0,0 +1,190 @@ +package proxyconfig + +import ( + "context" + "strings" + "testing" + + configv1 "github.com/openshift/api/config/v1" + "github.com/openshift/cluster-network-operator/pkg/client/fake" + "github.com/openshift/cluster-network-operator/pkg/controller/statusmanager" + "github.com/openshift/cluster-network-operator/pkg/names" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + + crclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func init() { + if err := configv1.Install(scheme.Scheme); err != nil { + panic(err) + } +} + +func TestEnqueueProxy(t *testing.T) { + for _, object := range []crclient.Object{ + &configv1.Network{}, + &configv1.Infrastructure{}, + } { + requests := enqueueProxy(context.Background(), object) + if len(requests) != 1 { + t.Fatalf("expected one reconcile request for %T, got %d", object, len(requests)) + } + if requests[0].NamespacedName != names.Proxy() { + t.Errorf("expected request %v for %T, got %v", names.Proxy(), object, requests[0].NamespacedName) + } + } +} + +func TestReconcileUpdatesProxyStatusFromDependencies(t *testing.T) { + const ( + initialCIDR = "10.128.0.0/14" + updatedCIDR = "10.128.0.0/13" + initialAPIServer = "api-int.initial.example.com" + updatedAPIServer = "api-int.updated.example.com" + ) + + proxy := proxyWithSpec(configv1.ProxySpec{ + HTTPProxy: "http://proxy.example.com:3128", + HTTPSProxy: "http://proxy.example.com:3128", + }) + network := networkWithClusterCIDR(initialCIDR) + infrastructure := infrastructureWithAPIServer(initialAPIServer) + client, reconciler := newProxyConfigReconciler(t, proxy, network, infrastructure) + + reconcileProxyConfig(t, reconciler) + assertNoProxyContains(t, getProxyStatus(t, client).NoProxy, initialCIDR, initialAPIServer) + + network.Status.ClusterNetwork = []configv1.ClusterNetworkEntry{{CIDR: updatedCIDR}} + if err := client.Update(context.Background(), network); err != nil { + t.Fatalf("failed to update Network: %v", err) + } + reconcileMappedRequest(t, reconciler, network) + + noProxy := getProxyStatus(t, client).NoProxy + assertNoProxyContains(t, noProxy, updatedCIDR) + assertNoProxyExcludes(t, noProxy, initialCIDR) + + infrastructure.Status.APIServerInternalURL = "https://" + updatedAPIServer + ":6443" + if err := client.Update(context.Background(), infrastructure); err != nil { + t.Fatalf("failed to update Infrastructure: %v", err) + } + reconcileMappedRequest(t, reconciler, infrastructure) + + noProxy = getProxyStatus(t, client).NoProxy + assertNoProxyContains(t, noProxy, updatedAPIServer) + assertNoProxyExcludes(t, noProxy, initialAPIServer) +} + +func newProxyConfigReconciler(t *testing.T, objects ...crclient.Object) (crclient.Client, *ReconcileProxyConfig) { + t.Helper() + + objects = append(objects, clusterConfigMap()) + fakeClient := fake.NewFakeClient(objects...) + return fakeClient.Default().CRClient(), &ReconcileProxyConfig{ + client: fakeClient.Default().CRClient(), + status: statusmanager.New(fakeClient, "network", names.StandAloneClusterName), + } +} + +func reconcileMappedRequest(t *testing.T, reconciler *ReconcileProxyConfig, object crclient.Object) { + t.Helper() + + requests := enqueueProxy(context.Background(), object) + if len(requests) != 1 { + t.Fatalf("expected one mapped reconcile request, got %d", len(requests)) + } + reconcileRequest(t, reconciler, requests[0]) +} + +func reconcileProxyConfig(t *testing.T, reconciler *ReconcileProxyConfig) { + t.Helper() + reconcileRequest(t, reconciler, reconcile.Request{NamespacedName: names.Proxy()}) +} + +func reconcileRequest(t *testing.T, reconciler *ReconcileProxyConfig, request reconcile.Request) { + t.Helper() + if _, err := reconciler.Reconcile(context.Background(), request); err != nil { + t.Fatalf("reconcile failed: %v", err) + } +} + +func getProxyStatus(t *testing.T, client crclient.Client) configv1.ProxyStatus { + t.Helper() + + proxy := &configv1.Proxy{} + if err := client.Get(context.Background(), names.Proxy(), proxy); err != nil { + t.Fatalf("failed to get Proxy: %v", err) + } + return proxy.Status +} + +func assertNoProxyContains(t *testing.T, noProxy string, values ...string) { + t.Helper() + for _, value := range values { + if !strings.Contains(noProxy, value) { + t.Errorf("expected Proxy status noProxy to contain %q, got %q", value, noProxy) + } + } +} + +func assertNoProxyExcludes(t *testing.T, noProxy string, values ...string) { + t.Helper() + for _, value := range values { + if strings.Contains(noProxy, value) { + t.Errorf("expected Proxy status noProxy to exclude %q, got %q", value, noProxy) + } + } +} + +func proxyWithSpec(spec configv1.ProxySpec) *configv1.Proxy { + return &configv1.Proxy{ + ObjectMeta: metav1.ObjectMeta{Name: names.PROXY_CONFIG}, + Spec: spec, + } +} + +func networkWithClusterCIDR(cidr string) *configv1.Network { + return &configv1.Network{ + ObjectMeta: metav1.ObjectMeta{Name: names.CLUSTER_CONFIG}, + Status: configv1.NetworkStatus{ + ClusterNetwork: []configv1.ClusterNetworkEntry{{CIDR: cidr}}, + ServiceNetwork: []string{"172.30.0.0/16"}, + }, + } +} + +func infrastructureWithAPIServer(host string) *configv1.Infrastructure { + return &configv1.Infrastructure{ + ObjectMeta: metav1.ObjectMeta{Name: names.CLUSTER_CONFIG}, + Status: configv1.InfrastructureStatus{ + APIServerInternalURL: "https://" + host + ":6443", + PlatformStatus: &configv1.PlatformStatus{ + Type: configv1.AWSPlatformType, + AWS: &configv1.AWSPlatformStatus{ + Region: "us-east-1", + }, + }, + }, + } +} + +func clusterConfigMap() *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-config-v1", + Namespace: "kube-system", + }, + Data: map[string]string{ + "install-config": ` +controlPlane: + replicas: "3" +networking: + machineCIDR: 10.0.0.0/16 +`, + }, + } +}