From e71ce1e969e301507ca72a995e98fa81724cf1fd Mon Sep 17 00:00:00 2001 From: awgreene Date: Tue, 6 Aug 2019 15:44:14 -0400 Subject: [PATCH] [CA] Use Certificate Authority bundle This commit creates a controller that watches for events on the "marketplace-operator" ConfigMap. In the event that the ConfigMap is updated, the marketplace operator will restart, reconciling existing operands and recreate their deployments with the correct CA certificates. --- deploy/configmap.yaml | 9 ++ deploy/operator.yaml | 11 +++ deploy/role.yaml | 8 ++ manifests/05_role.yaml | 8 ++ manifests/07_configmap.yaml | 9 ++ .../{07_operator.yaml => 08_operator.yaml} | 11 +++ ...roperator.yaml => 09_clusteroperator.yaml} | 0 pkg/certificateauthority/mount.go | 52 ++++++++++ pkg/controller/add_configmap.go | 10 ++ .../configmap/configmap_controller.go | 98 +++++++++++++++++++ pkg/registry/registry.go | 8 ++ 11 files changed, 224 insertions(+) create mode 100644 deploy/configmap.yaml create mode 100644 manifests/07_configmap.yaml rename manifests/{07_operator.yaml => 08_operator.yaml} (83%) rename manifests/{08_clusteroperator.yaml => 09_clusteroperator.yaml} (100%) create mode 100644 pkg/certificateauthority/mount.go create mode 100644 pkg/controller/add_configmap.go create mode 100644 pkg/controller/configmap/configmap_controller.go diff --git a/deploy/configmap.yaml b/deploy/configmap.yaml new file mode 100644 index 000000000..436b4aeb9 --- /dev/null +++ b/deploy/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + # This label ensures that the OpenShift Certificate Authority bundle + # is added to the ConfigMap. + config.openshift.io/inject-trusted-cabundle: "true" + name: marketplace-trusted-ca + namespace: openshift-marketplace diff --git a/deploy/operator.yaml b/deploy/operator.yaml index cff18f913..87b74a820 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -67,3 +67,14 @@ spec: - name: "RELEASE_VERSION" # The string "0.0.1-snapshot" is substituted by the CVO with the version of the payload value: "0.0.1-snapshot" + volumeMounts: + - name: marketplace-trusted-ca + mountPath: /etc/pki/ca-trust/extracted/pem/ + volumes: + - name: marketplace-trusted-ca + configMap: + name: marketplace-trusted-ca + items: + # Require that this data is present in the ConfigMap before the operator is deployed. + - key: ca-bundle.crt + path: tls-ca-bundle.pem diff --git a/deploy/role.yaml b/deploy/role.yaml index b18883a98..a898764a0 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -3,6 +3,14 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: marketplace-operator rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: diff --git a/manifests/05_role.yaml b/manifests/05_role.yaml index b18883a98..a898764a0 100644 --- a/manifests/05_role.yaml +++ b/manifests/05_role.yaml @@ -3,6 +3,14 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: marketplace-operator rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: diff --git a/manifests/07_configmap.yaml b/manifests/07_configmap.yaml new file mode 100644 index 000000000..436b4aeb9 --- /dev/null +++ b/manifests/07_configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + # This label ensures that the OpenShift Certificate Authority bundle + # is added to the ConfigMap. + config.openshift.io/inject-trusted-cabundle: "true" + name: marketplace-trusted-ca + namespace: openshift-marketplace diff --git a/manifests/07_operator.yaml b/manifests/08_operator.yaml similarity index 83% rename from manifests/07_operator.yaml rename to manifests/08_operator.yaml index a09f00b7f..edfabe9ae 100644 --- a/manifests/07_operator.yaml +++ b/manifests/08_operator.yaml @@ -67,3 +67,14 @@ spec: - name: "RELEASE_VERSION" # The string "0.0.1-snapshot" is substituted by the CVO with the version of the payload value: "0.0.1-snapshot" + volumeMounts: + - name: marketplace-trusted-ca + mountPath: /etc/pki/ca-trust/extracted/pem/ + volumes: + - name: marketplace-trusted-ca + configMap: + name: marketplace-trusted-ca + items: + # Require that this data is present in the ConfigMap before the operator is deployed. + - key: ca-bundle.crt + path: tls-ca-bundle.pem diff --git a/manifests/08_clusteroperator.yaml b/manifests/09_clusteroperator.yaml similarity index 100% rename from manifests/08_clusteroperator.yaml rename to manifests/09_clusteroperator.yaml diff --git a/pkg/certificateauthority/mount.go b/pkg/certificateauthority/mount.go new file mode 100644 index 000000000..5dc6ce1f2 --- /dev/null +++ b/pkg/certificateauthority/mount.go @@ -0,0 +1,52 @@ +package certificateauthority + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +const ( + // TrustedCaConfigMapName is the name of the Marketplace ConfigMap that store Certificate Authority information. + TrustedCaConfigMapName = "marketplace-trusted-ca" + + // TrustedCaMountPath is the path to the directory where the Certificate Authority should be mounted. + TrustedCaMountPath = "/etc/pki/ca-trust/extracted/pem/" + + // The key value that stores Certificate Authorities. + caBundleKey = "ca-bundle.crt" + + // The path where we will mount the Certificate Authorities. + caBundlePath = "tls-ca-bundle.pem" +) + +// MountConfigMap creates a Volume and VolumeMount for a ConfigMap of the same name and +// adds it to a deployment. +func MountConfigMap(name, mountPath string, deployment *appsv1.Deployment) { + // Create and add the Volume to the deployment. + deployment.Spec.Template.Spec.Volumes = []corev1.Volume{ + corev1.Volume{ + Name: name, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: name, + }, + Items: []corev1.KeyToPath{ + corev1.KeyToPath{ + Key: caBundleKey, + Path: caBundlePath, + }, + }, + }, + }, + }, + } + + // Create and add the VolumeMount to the first container in a deployment. + deployment.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{ + corev1.VolumeMount{ + Name: name, + MountPath: mountPath, + }, + } +} diff --git a/pkg/controller/add_configmap.go b/pkg/controller/add_configmap.go new file mode 100644 index 000000000..23016b22d --- /dev/null +++ b/pkg/controller/add_configmap.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/operator-framework/operator-marketplace/pkg/controller/configmap" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, configmap.Add) +} diff --git a/pkg/controller/configmap/configmap_controller.go b/pkg/controller/configmap/configmap_controller.go new file mode 100644 index 000000000..c08033b01 --- /dev/null +++ b/pkg/controller/configmap/configmap_controller.go @@ -0,0 +1,98 @@ +package configmap + +import ( + "os" + + mktconfig "github.com/operator-framework/operator-marketplace/pkg/apis/config/v1" + "github.com/operator-framework/operator-marketplace/pkg/apis/operators/shared" + ca "github.com/operator-framework/operator-marketplace/pkg/certificateauthority" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// Add creates a new ConfigMap Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new ReconcileConfigMap. +func newReconciler(mgr manager.Manager) *ReconcileConfigMap { + return &ReconcileConfigMap{} +} + +// add adds a new Controller to mgr with r as the ReconcileConfigMap. +func add(mgr manager.Manager, r *ReconcileConfigMap) error { + if !mktconfig.IsAPIAvailable() { + return nil + } + + // Create a new controller + c, err := controller.New("configmap-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource ConfigMap. + err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForObject{}, getPredicateFunctions()) + if err != nil { + return err + } + + return nil +} + +// getPredicateFunctions returns the predicate functions used to identify the configmap +// that contains Certificate Authority information. +// True should only be returned when the ConfigMap is updated by the cert-injector-controller. +func getPredicateFunctions() predicate.Funcs { + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + // If the ConfigMap is ever changed we should kick off an event. + if e.MetaOld.GetName() == ca.TrustedCaConfigMapName { + return true + } + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + } +} + +var _ reconcile.Reconciler = &ReconcileConfigMap{} + +// ReconcileConfigMap reconciles a ConfigMap object. +type ReconcileConfigMap struct { +} + +// Reconcile will restart the marketplace operator. +func (r *ReconcileConfigMap) Reconcile(request reconcile.Request) (reconcile.Result, error) { + // Check if the CA ConfigMap is in the same namespace that Marketplace is deployed in. + objectInOtherNamespace, err := shared.IsObjectInOtherNamespace(request.Namespace) + if err != nil { + return reconcile.Result{}, err + } + + // If the CA ConfigMap is in the same namespace we should restart marketplace. + if !objectInOtherNamespace { + log.Infof("Certificate Authorization ConfigMap %s/%s has been updated, restarting marketplace.", request.Namespace, request.Name) + os.Exit(0) + } + + // Otherwise ignore the event. + return reconcile.Result{}, nil +} diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 0d6018f98..8830a40dd 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -6,9 +6,11 @@ import ( "strconv" "time" + configv1 "github.com/operator-framework/operator-marketplace/pkg/apis/config/v1" "github.com/operator-framework/operator-marketplace/pkg/apis/operators/v1" "github.com/operator-framework/operator-marketplace/pkg/apis/operators/v2" "github.com/operator-framework/operator-marketplace/pkg/builders" + ca "github.com/operator-framework/operator-marketplace/pkg/certificateauthority" wrapper "github.com/operator-framework/operator-marketplace/pkg/client" "github.com/operator-framework/operator-marketplace/pkg/datastore" "github.com/operator-framework/operator-marketplace/pkg/proxy" @@ -141,6 +143,12 @@ func (r *registry) ensureDeployment(appRegistries []string, needServiceAccount b // Update proxy environment variables to match those in the operator. deployment.Spec.Template.Spec.Containers[0].Env = proxy.GetProxyEnvVars() } + + // Mount the Certificate Authority into the deployment. + if configv1.IsAPIAvailable() { + ca.MountConfigMap(ca.TrustedCaConfigMapName, ca.TrustedCaMountPath, deployment) + } + // Set or update the annotation to force an update. This is required so that we get updates // from Quay during the sync cycle when packages have not been added or removed from the spec. meta.SetMetaDataAnnotation(&deployment.Spec.Template.ObjectMeta, deploymentUpdateAnnotation,