/
controller.go
184 lines (158 loc) · 6.29 KB
/
controller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
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
// IsAlpha tells the client to disregard "no matching kind" type of errors
IsAlpha() bool
}
// 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")
}