183 lines
6.2 KiB
Go
183 lines
6.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"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-logr/logr"
|
|
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"
|
|
|
|
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
|
|
logf "github.com/jetstack/cert-manager/pkg/logs"
|
|
)
|
|
|
|
// 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.Annotations[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
|
|
// sources is a list of available 'data sources' that can be used to extract
|
|
// caBundles from various source.
|
|
// This is defined as a variable to allow an instance of the secret-based
|
|
// cainjector to run even when Certificate resources cannot we watched due to
|
|
// the conversion webhook not being available.
|
|
sources []caDataSource
|
|
|
|
log logr.Logger
|
|
client.Client
|
|
|
|
resourceName string // just used for logging
|
|
}
|
|
|
|
// 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 {
|
|
if dropNotFound(err) == nil {
|
|
// don't requeue on deletions, which yield a non-found object
|
|
return ctrl.Result{}, 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
|
|
}
|
|
log = logf.WithResource(r.log, metaObj)
|
|
|
|
dataSource, err := r.caDataSourceFor(log, metaObj)
|
|
if err != nil {
|
|
log.V(4).Info("failed to determine ca data source for injectable")
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
caData, err := dataSource.ReadCA(ctx, log, metaObj)
|
|
if err != nil {
|
|
log.Error(err, "failed to read CA from data source")
|
|
return ctrl.Result{}, err
|
|
}
|
|
if caData == nil {
|
|
log.Info("could not find any ca data in data source for target")
|
|
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
|
|
}
|
|
|
|
func (r *genericInjectReconciler) caDataSourceFor(log logr.Logger, metaObj metav1.Object) (caDataSource, error) {
|
|
for _, s := range r.sources {
|
|
if s.Configured(log, metaObj) {
|
|
return s, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("could not determine ca data source for resource")
|
|
}
|