Skip to content

Commit

Permalink
Adds a CA bundle injector
Browse files Browse the repository at this point in the history
This adds the ability to inject a combined CA bundle into configmaps that are setup to want them
  • Loading branch information
JacobTanenbaum committed Aug 13, 2019
1 parent 0b6b46a commit 3520332
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 17 deletions.
4 changes: 4 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/controller/add_networkconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controller

import (
"github.com/openshift/cluster-network-operator/pkg/controller/clusterconfig"
"github.com/openshift/cluster-network-operator/pkg/controller/configmap_ca_injector"
"github.com/openshift/cluster-network-operator/pkg/controller/operconfig"
"github.com/openshift/cluster-network-operator/pkg/controller/proxyconfig"
)
Expand All @@ -13,5 +14,6 @@ func init() {
operconfig.Add,
clusterconfig.Add,
operconfig.AddConfigMapReconciler,
configmapcainjector.Add,
)
}
189 changes: 189 additions & 0 deletions pkg/controller/configmap_ca_injector/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package configmapcainjector

import (
"context"
"fmt"
"log"

configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/cluster-network-operator/pkg/controller/statusmanager"
"github.com/openshift/cluster-network-operator/pkg/names"
"github.com/openshift/cluster-network-operator/pkg/util/validation"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"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"
)

func Add(mgr manager.Manager, status *statusmanager.StatusManager) error {
reconciler := newReconciler(mgr, status)
if reconciler == nil {
return fmt.Errorf("failed to create reconciler")
}

return add(mgr, reconciler)
}

func newReconciler(mgr manager.Manager, status *statusmanager.StatusManager) reconcile.Reconciler {
if err := configv1.Install(mgr.GetScheme()); err != nil {
return nil
}

return &ReconcileConfigMapInjector{client: mgr.GetClient(), scheme: mgr.GetScheme(), status: status}
}

func add(mgr manager.Manager, r reconcile.Reconciler) error {
// Create a new controller
c, err := controller.New("configmap-trust-bundle-injector-controller", mgr, controller.Options{Reconciler: r})
if err != nil {
return err
}

// The events fire for changes/creation of the trusted-ca-bundle and any configmaps with the
//"config.openshift.io/inject-trusted-cabundle" label
pred := predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
return shouldUpdateConfigMaps(e.MetaNew)
},
DeleteFunc: func(e event.DeleteEvent) bool {
return false
},
CreateFunc: func(e event.CreateEvent) bool {
return shouldUpdateConfigMaps(e.Meta)
},
GenericFunc: func(e event.GenericEvent) bool {
return shouldUpdateConfigMaps(e.Meta)
},
}

err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForObject{}, pred)
if err != nil {
return err
}

return nil
}

var _ reconcile.Reconciler = &ReconcileConfigMapInjector{}

type ReconcileConfigMapInjector struct {
client client.Client
scheme *runtime.Scheme
status *statusmanager.StatusManager
}

// Reconcile expectes requests to refers to configmaps of two different types.
// 1. a configmap named trusted-ca-bundle in namespace openshift-config-managed and will ensure that all configmaps with the label
// config.openshift.io/inject-trusted-cabundle = true have the certificate information stored in trusted-ca-bundle's ca-bundle.crt entry.
// 2. a configmap in any namespace with the label config.openshift.io/inject-trusted-cabundle = true and will insure that it contains the ca-bundle.crt
// entry in the configmap named trusted-ca-bundle in namespace openshift-config-managed.
func (r *ReconcileConfigMapInjector) Reconcile(request reconcile.Request) (reconcile.Result, error) {
log.Printf("Reconciling configmap from %s/%s\n", request.Name, request.Namespace)

trustedCAbundleConfigMap := &corev1.ConfigMap{}
trustedCAbundleConfigMapName := types.NamespacedName{
Namespace: names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS,
Name: names.TRUSTED_CA_BUNDLE_CONFIGMAP,
}
err := r.client.Get(context.TODO(), trustedCAbundleConfigMapName, trustedCAbundleConfigMap)
if err != nil {
log.Println(err)
return reconcile.Result{}, err
}
_, trustedCAbundleData, err := validation.TrustBundleConfigMap(trustedCAbundleConfigMap)

if err != nil {
return reconcile.Result{}, err
}
//build a list of configMaps
configMapsToChange := []corev1.ConfigMap{}

//The trusted-ca-bundle changed
if request.Name == names.TRUSTED_CA_BUNDLE_CONFIGMAP && request.Namespace == names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS {

configMapList := &corev1.ConfigMapList{}
selector := labels.Set(map[string]string{names.TRUSTED_CA_BUNDLE_CONFIGMAP_LABEL: "true"}).AsSelector()
err = r.client.List(context.TODO(), &client.ListOptions{LabelSelector: selector}, configMapList)
if err != nil {
log.Println(err)
return reconcile.Result{}, err
}
configMapsToChange = configMapList.Items
log.Printf("%s changed, updating %d configMaps", names.TRUSTED_CA_BUNDLE_CONFIGMAP, len(configMapsToChange))
} else {
//changing a single labeled configmap

//get the requested object
requestedCAbundleConfigMap := &corev1.ConfigMap{}
requestedCAbundleConfigMapName := types.NamespacedName{
Namespace: request.Namespace,
Name: request.Name,
}
err = r.client.Get(context.TODO(), requestedCAbundleConfigMapName, requestedCAbundleConfigMap)
if err != nil {
log.Println(err)
if apierrors.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
configMapsToChange = append(configMapsToChange, *requestedCAbundleConfigMap)
}

errs := []error{}

for _, configMap := range configMapsToChange {
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
retrievedConfigMap := &corev1.ConfigMap{}
err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: configMap.Namespace, Name: configMap.Name}, retrievedConfigMap)
if err != nil {
log.Println(err)
return err
}
configMapToUpdate := retrievedConfigMap.DeepCopy()
if configMapToUpdate.Data == nil {
configMapToUpdate.Data = map[string]string{names.TRUSTED_CA_BUNDLE_CONFIGMAP_KEY: string(trustedCAbundleData)}
} else {
configMapToUpdate.Data[names.TRUSTED_CA_BUNDLE_CONFIGMAP_KEY] = string(trustedCAbundleData)
}
if equality.Semantic.DeepEqual(configMapToUpdate, retrievedConfigMap) {
// nothing to update the new and old configmap object would be the same
return nil
}
err = r.client.Update(context.TODO(), configMapToUpdate)
if err != nil {
log.Println(err)
return err
}
return nil
})
if err != nil {
errs = append(errs, err)
if len(errs) > 5 {
return reconcile.Result{}, fmt.Errorf("Too many errors attempting to update configmaps with CA cert. data")
}
}
}
if len(errs) > 0 {
return reconcile.Result{}, fmt.Errorf("some configmaps didn't fully update with CA cert. data")
}
return reconcile.Result{}, nil
}

func shouldUpdateConfigMaps(meta metav1.Object) bool {
return meta.GetLabels()[names.TRUSTED_CA_BUNDLE_CONFIGMAP_LABEL] == "true" ||
(meta.GetName() == names.TRUSTED_CA_BUNDLE_CONFIGMAP && meta.GetNamespace() == names.TRUSTED_CA_BUNDLE_CONFIGMAP_NS)
}
26 changes: 13 additions & 13 deletions pkg/names/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ const SERVICE_CA_CONFIGMAP = "openshift-service-ca"
// that is used in multus admission controller deployment
const MULTUS_VALIDATING_WEBHOOK = "multus.openshift.io"

// TRUST_BUNDLE_CONFIGMAP_KEY is the name of the data key containing
// TRUSTED_CA_BUNDLE_CONFIGMAP_KEY is the name of the data key containing
// the PEM encoded trust bundle.
const TRUST_BUNDLE_CONFIGMAP_KEY = "ca-bundle.crt"
const TRUSTED_CA_BUNDLE_CONFIGMAP_KEY = "ca-bundle.crt"

// TRUST_BUNDLE_CONFIGMAP is the name of the ConfigMap
// TRUSTED_CA_BUNDLE_CONFIGMAP is the name of the ConfigMap
// containing the combined user/system trust bundle.
const TRUST_BUNDLE_CONFIGMAP = "trusted-ca-bundle"
const TRUSTED_CA_BUNDLE_CONFIGMAP = "trusted-ca-bundle"

// TRUST_BUNDLE_CONFIGMAP_NS is the namespace that hosts the
// TRUSTED_CA_BUNDLE_CONFIGMAP_NS is the namespace that hosts the
// ADDL_TRUST_BUNDLE_CONFIGMAP and TRUST_BUNDLE_CONFIGMAP
// ConfigMaps.
const TRUST_BUNDLE_CONFIGMAP_NS = "openshift-config-managed"
const TRUSTED_CA_BUNDLE_CONFIGMAP_NS = "openshift-config-managed"

// TRUST_BUNDLE_CONFIGMAP_ANNOTATION is the name of the annotation that
// TRUSTED_CA_BUNDLE_CONFIGMAP_LABEL is the name of the label that
// determines whether or not to inject the combined ca certificate
const TRUST_BUNDLE_CONFIGMAP_ANNOTATION = "config.openshift.io/inject-trusted-cabundle"
const TRUSTED_CA_BUNDLE_CONFIGMAP_LABEL = "config.openshift.io/inject-trusted-cabundle"

// Proxy returns the namespaced name "cluster" in the
// default namespace.
Expand All @@ -62,11 +62,11 @@ func Proxy() types.NamespacedName {
}
}

// TrustBundleConfigMap returns the namespaced name of the ConfigMap
// containing the merged user/system trust bundle.
func TrustBundleConfigMap() types.NamespacedName {
// AddlTrustBundleConfigMapNS returns the namespaced name of the
// namespace containing the user-provided trust bundle ConfigMap.
func AddlTrustBundleConfigMap() types.NamespacedName {
return types.NamespacedName{
Namespace: TRUST_BUNDLE_CONFIGMAP_NS,
Name: TRUST_BUNDLE_CONFIGMAP,
Namespace: TRUSTED_CA_BUNDLE_CONFIGMAP_NS,
Name: TRUSTED_CA_BUNDLE_CONFIGMAP,
}
}
8 changes: 4 additions & 4 deletions pkg/util/validation/trustbundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ const (
// contains one or more valid PEM encoded certificates, returning
// a byte slice of "ca-bundle.crt" contents upon success.
func TrustBundleConfigMap(cfgMap *corev1.ConfigMap) ([]*x509.Certificate, []byte, error) {
if _, ok := cfgMap.Data[names.TRUST_BUNDLE_CONFIGMAP_KEY]; !ok {
return nil, nil, fmt.Errorf("ConfigMap %q is missing %q", cfgMap.Name, names.TRUST_BUNDLE_CONFIGMAP_KEY)
if _, ok := cfgMap.Data[names.TRUSTED_CA_BUNDLE_CONFIGMAP_KEY]; !ok {
return nil, nil, fmt.Errorf("ConfigMap %q is missing %q", cfgMap.Name, names.TRUSTED_CA_BUNDLE_CONFIGMAP_KEY)
}
trustBundleData := []byte(cfgMap.Data[names.TRUST_BUNDLE_CONFIGMAP_KEY])
trustBundleData := []byte(cfgMap.Data[names.TRUSTED_CA_BUNDLE_CONFIGMAP_KEY])
if len(trustBundleData) == 0 {
return nil, nil, fmt.Errorf("data key %q is empty from ConfigMap %q", names.TRUST_BUNDLE_CONFIGMAP_KEY, cfgMap.Name)
return nil, nil, fmt.Errorf("data key %q is empty from ConfigMap %q", names.TRUSTED_CA_BUNDLE_CONFIGMAP_KEY, cfgMap.Name)
}
certBundle, _, err := CertificateData(trustBundleData)
if err != nil {
Expand Down

0 comments on commit 3520332

Please sign in to comment.