-
Notifications
You must be signed in to change notification settings - Fork 246
Add CA bundle injector #274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
openshift-merge-robot
merged 3 commits into
openshift:master
from
JacobTanenbaum:SDN-496
Aug 15, 2019
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
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/api/errors" | ||
"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 | ||
// label "config.openshift.io/inject-trusted-cabundle". | ||
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 expects 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 { | ||
if errors.IsNotFound(err) { | ||
return reconcile.Result{}, 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{} | ||
squeed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// 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 | ||
JacobTanenbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
configMapsToChange = configMapList.Items | ||
JacobTanenbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
log.Printf("%s changed, updating %d configMaps", names.TRUSTED_CA_BUNDLE_CONFIGMAP, len(configMapsToChange)) | ||
} else { | ||
JacobTanenbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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) | ||
JacobTanenbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 { | ||
JacobTanenbaum marked this conversation as resolved.
Show resolved
Hide resolved
squeed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
retrievedConfigMap := &corev1.ConfigMap{} | ||
err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: configMap.Namespace, Name: configMap.Name}, retrievedConfigMap) | ||
if err != nil { | ||
if errors.IsNotFound(err) { | ||
return nil | ||
} | ||
log.Println(err) | ||
return err | ||
JacobTanenbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
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) | ||
JacobTanenbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package validation | ||
|
||
import ( | ||
"crypto/x509" | ||
"encoding/pem" | ||
"fmt" | ||
|
||
"github.com/openshift/cluster-network-operator/pkg/names" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
) | ||
|
||
const ( | ||
// certPEMBlock is the type taken from the preamble of a PEM-encoded structure. | ||
certPEMBlock = "CERTIFICATE" | ||
) | ||
|
||
// TrustBundleConfigMap validates that ConfigMap contains a | ||
// trust bundle named "ca-bundle.crt" and that "ca-bundle.crt" | ||
// 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.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.TRUSTED_CA_BUNDLE_CONFIGMAP_KEY]) | ||
if len(trustBundleData) == 0 { | ||
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 { | ||
return nil, nil, fmt.Errorf("failed parsing certificate data from ConfigMap %q: %v", cfgMap.Name, err) | ||
} | ||
|
||
return certBundle, trustBundleData, nil | ||
} | ||
|
||
// CertificateData decodes certData, ensuring each PEM block is type | ||
// "CERTIFICATE" and the block can be parsed as an x509 certificate, | ||
// returning slices of parsed certificates and parsed certificate data. | ||
func CertificateData(certData []byte) ([]*x509.Certificate, []byte, error) { | ||
var block *pem.Block | ||
certBundle := []*x509.Certificate{} | ||
for len(certData) != 0 { | ||
block, certData = pem.Decode(certData) | ||
if block == nil { | ||
return nil, nil, fmt.Errorf("failed to parse certificate PEM") | ||
} | ||
if block.Type != certPEMBlock { | ||
return nil, nil, fmt.Errorf("invalid certificate PEM, must be of type %q", certPEMBlock) | ||
|
||
} | ||
|
||
cert, err := x509.ParseCertificate(block.Bytes) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("failed to parse certificate: %v", err) | ||
} | ||
certBundle = append(certBundle, cert) | ||
} | ||
|
||
return certBundle, certData, nil | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.