cert-manager/pkg/controller/cainjector/controller.go
James Munnelly 1618ebde43 Fix loading apiserver caBundle
Signed-off-by: James Munnelly <james@munnelly.eu>
2019-02-28 19:34:40 +00:00

228 lines
8.2 KiB
Go

/*
Copyright 2019 The Jetstack cert-manager contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cainjector
import (
"context"
"strings"
"github.com/go-logr/logr"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
certctrl "github.com/jetstack/cert-manager/pkg/controller/certificates"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var (
// WantInjectAnnotation is the annotation that specifies that a particular
// object wants injection of CAs. It takes the form of a reference to a certificate
// as namespace/name. The certificate is expected to have the is-serving-for annotations.
WantInjectAnnotation = "certmanager.k8s.io/inject-ca-from"
// WantInjectAPIServerCAAnnotation, if set to "true", will make the cainjector
// inject the CA certificate for the Kubernetes apiserver into the resource.
// It discovers the apiserver's CA by inspecting the service account credentials
// mounted into the
WantInjectAPIServerCAAnnotation = "certmanager.k8s.io/inject-apiserver-ca"
)
// dropNotFound ignores the given error if it's a not-found error,
// but otherwise just returns the argument.
func dropNotFound(err error) error {
if apierrors.IsNotFound(err) {
return nil
}
return err
}
// OwningCertForSecret gets the name of the owning certificate for a
// given secret, returning nil if no such object exists.
// Right now, this actually uses a label instead of owner refs,
// since certmanager doesn't set owner refs on secrets.
func OwningCertForSecret(secret *corev1.Secret) *types.NamespacedName {
lblVal, hasLbl := secret.Labels[certmanager.CertificateNameKey]
if !hasLbl {
return nil
}
return &types.NamespacedName{
Name: lblVal,
Namespace: secret.Namespace,
}
}
// InjectTarget is a Kubernetes API object that has one or more references to Kubernetes
// Services with corresponding fields for CA bundles.
type InjectTarget interface {
// AsObject returns this injectable as an object.
// It should be a pointer suitable for mutation.
AsObject() runtime.Object
// SetCA sets the CA of this target to the given certificate data (in the standard
// PEM format used across Kubernetes). In cases where multiple CA fields exist per
// target (like admission webhook configs), all CAs are set to the given value.
SetCA(data []byte)
}
// Injectable is a point in a Kubernetes API object that represents a Kubernetes Service
// reference with a corresponding spot for a CA bundle.
type Injectable interface {
}
// CertInjector knows how to create an instance of an InjectTarget for some particular type
// of inject target. For instance, an implementation might create a InjectTarget
// containing an empty MutatingWebhookConfiguration. The underlying API object can
// be populated (via AsObject) using client.Client#Get, and then CAs can be injected with
// Injectables (representing the various individual webhooks in the config) retrieved with
// Services.
type CertInjector interface {
// NewTarget creates a new InjectTarget containing an empty underlying object.
NewTarget() InjectTarget
}
// genericInjectReconciler is a reconciler that knows how to check if a given object is
// marked as requiring a CA, chase down the corresponding Service, Certificate, Secret, and
// inject that into the object.
type genericInjectReconciler struct {
// injector is responsible for the logic of actually setting a CA -- it's the component
// that contains type-specific logic.
injector CertInjector
log logr.Logger
client.Client
// apiserverCABundle is the ca bundle used by the apiserver.
// This will be injected into resources that have the `
apiserverCABundle []byte
resourceName string // just used for logging
}
// InjectClient provides a client for this reconciler.
func (r *genericInjectReconciler) InjectClient(c client.Client) error {
r.Client = c
return nil
}
// splitNamespacedName turns the string form of a namespaced name
// (<namespace>/<name>) back into a types.NamespacedName.
func splitNamespacedName(nameStr string) types.NamespacedName {
splitPoint := strings.IndexRune(nameStr, types.Separator)
if splitPoint == -1 {
return types.NamespacedName{Name: nameStr}
}
return types.NamespacedName{Namespace: nameStr[:splitPoint], Name: nameStr[splitPoint+1:]}
}
// Reconcile attempts to ensure that a particular object has all the CAs injected that
// it has requested.
func (r *genericInjectReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
log := r.log.WithValues(r.resourceName, req.NamespacedName)
// fetch the target object
target := r.injector.NewTarget()
if err := r.Client.Get(ctx, req.NamespacedName, target.AsObject()); err != nil {
log.Error(err, "unable to fetch target object to inject into")
return ctrl.Result{}, err
}
// ensure that it wants injection
metaObj, err := meta.Accessor(target.AsObject())
if err != nil {
log.Error(err, "unable to get metadata for object")
return ctrl.Result{}, err
}
certNameRaw := metaObj.GetAnnotations()[WantInjectAnnotation]
hasInjectAPIServerCA := metaObj.GetAnnotations()[WantInjectAPIServerCAAnnotation] == "true"
if certNameRaw != "" && hasInjectAPIServerCA {
log.Info("object has both inject-ca-from and inject-apiserver-ca annotations, skipping")
return ctrl.Result{}, nil
}
if hasInjectAPIServerCA {
log.V(1).Info("setting apiserver ca bundle on injectable")
target.SetCA(r.apiserverCABundle)
// actually update with injected CA data
if err := r.Client.Update(ctx, target.AsObject()); err != nil {
log.Error(err, "unable to update target object with new CA data")
return ctrl.Result{}, err
}
log.V(1).Info("updated object")
return ctrl.Result{}, nil
}
if certNameRaw == "" {
log.V(1).Info("object does not want CA injection, skipping")
return ctrl.Result{}, nil
}
certName := splitNamespacedName(certNameRaw)
log = log.WithValues("certificate", certName)
if certName.Namespace == "" {
log.Error(nil, "invalid certificate name")
// don't return an error, requeuing won't help till this is changed
return ctrl.Result{}, nil
}
var cert certmanager.Certificate
if err := r.Client.Get(ctx, certName, &cert); err != nil {
log.Error(err, "unable to fetch associated certificate")
// don't requeue if we're just not found, we'll get called when the secret gets created
return ctrl.Result{}, dropNotFound(err)
}
// grab the associated secret, and ensure it's owned by the cert
secretName := types.NamespacedName{Namespace: cert.Namespace, Name: cert.Spec.SecretName}
log = log.WithValues("secret", secretName)
var secret corev1.Secret
if err := r.Client.Get(ctx, secretName, &secret); err != nil {
log.Error(err, "unable to fetch associated secret")
// don't requeue if we're just not found, we'll get called when the secret gets created
return ctrl.Result{}, dropNotFound(err)
}
owner := OwningCertForSecret(&secret)
if owner == nil || *owner != certName {
log.Info("refusing to target secret not owned by certificate", "owner", metav1.GetControllerOf(&secret))
return ctrl.Result{}, nil
}
// inject the CA data
caData, hasCAData := secret.Data[certctrl.TLSCAKey]
if !hasCAData {
log.Error(nil, "certificate has no CA data")
// don't requeue, we'll get called when the secret gets updated
return ctrl.Result{}, nil
}
// actually do the injection
target.SetCA(caData)
// actually update with injected CA data
if err := r.Client.Update(ctx, target.AsObject()); err != nil {
log.Error(err, "unable to update target object with new CA data")
return ctrl.Result{}, err
}
log.V(1).Info("updated object")
return ctrl.Result{}, nil
}