-
Notifications
You must be signed in to change notification settings - Fork 91
/
controller.go
263 lines (230 loc) · 9.76 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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
package routercerts
import (
"bytes"
"context"
"crypto/x509"
"fmt"
"time"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
corev1informers "k8s.io/client-go/informers/core/v1"
corev1clients "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/klog/v2"
operatorv1 "github.com/openshift/api/operator/v1"
configv1informers "github.com/openshift/client-go/config/informers/externalversions/config/v1"
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/crypto"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/management"
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
"github.com/openshift/library-go/pkg/operator/v1helpers"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/common"
)
const (
conditionRouterCertsDegradedType = "RouterCertsDegraded"
)
// routerCertsDomainValidationController validates that router certs match the ingress domain
type routerCertsDomainValidationController struct {
operatorClient v1helpers.OperatorClient
secretsClient corev1clients.SecretsGetter
ingressLister configv1listers.IngressLister
secretLister corev1listers.SecretLister
configMapLister corev1listers.ConfigMapLister
secretNamespace string
defaultSecretName string
customSecretName string
routeName string
systemCertPool func() (*x509.CertPool, error) // enables unit testing
}
func NewRouterCertsDomainValidationController(
operatorClient v1helpers.OperatorClient,
secretsClient corev1clients.SecretsGetter,
eventRecorder events.Recorder,
ingressInformer configv1informers.IngressInformer,
targetNSsecretInformer corev1informers.SecretInformer,
machineConfigNSSecretInformer corev1informers.SecretInformer,
configMapInformer corev1informers.ConfigMapInformer,
secretNamespace string,
defaultSecretName string,
customSecretName string,
routeName string,
) factory.Controller {
controller := &routerCertsDomainValidationController{
operatorClient: operatorClient,
secretsClient: secretsClient,
ingressLister: ingressInformer.Lister(),
secretLister: targetNSsecretInformer.Lister(),
configMapLister: configMapInformer.Lister(),
secretNamespace: secretNamespace,
defaultSecretName: defaultSecretName,
customSecretName: customSecretName,
routeName: routeName,
systemCertPool: x509.SystemCertPool,
}
return factory.New().
WithInformers(
operatorClient.Informer(),
ingressInformer.Informer(),
targetNSsecretInformer.Informer(),
configMapInformer.Informer()).
WithFilteredEventsInformers(
factory.NamesFilter("router-certs"),
machineConfigNSSecretInformer.Informer(),
).
WithSync(controller.sync).
WithSyncDegradedOnError(operatorClient).
ResyncEvery(wait.Jitter(time.Minute, 1.0)).
ToController("RouterCertsDomainValidationController", eventRecorder)
}
func (c *routerCertsDomainValidationController) sync(ctx context.Context, syncCtx factory.SyncContext) (err error) {
spec, _, _, err := c.operatorClient.GetOperatorState()
if err != nil {
return err
}
if !management.IsOperatorManaged(spec.ManagementState) {
return nil
}
// set the condition anywhere in sync() to update the controller's degraded condition
var condition operatorv1.OperatorCondition
defer func() {
_, _, err = v1helpers.UpdateStatus(ctx, c.operatorClient, v1helpers.UpdateConditionFn(condition))
}()
// get ingress
ingress, err := c.ingressLister.Get("cluster")
if err != nil {
condition = newRouterCertsDegradedf("NoIngressConfig", "ingresses.config.openshift.io/cluster could not be retrieved: %v", err)
return nil
}
// add syncing for router certs for all cluster ingresses
if _, _, err := resourceapply.SyncPartialSecret(
ctx,
c.secretsClient,
syncCtx.Recorder(),
"openshift-config-managed", "router-certs",
"openshift-authentication", "v4-0-config-system-router-certs",
sets.New(ingress.Spec.Domain),
nil,
); err != nil {
return err
}
condition = c.validateRouterCertificates()
return nil
}
func (c *routerCertsDomainValidationController) validateRouterCertificates() operatorv1.OperatorCondition {
// get ingress
ingress, err := c.ingressLister.Get("cluster")
if err != nil {
return newRouterCertsDegradedf("NoIngressConfig", "ingresses.config.openshift.io/cluster could not be retrieved: %v", err)
}
// get ingress domain
ingressDomain := ingress.Spec.Domain
if len(ingressDomain) == 0 {
return newRouterCertsDegradedf("NoIngressDomain", "ingresses.config.openshift.io/cluster: no spec.domain specified")
}
// get router certs secret
secret, err := common.GetActiveRouterSecret(c.secretLister, c.secretNamespace, c.defaultSecretName, c.customSecretName)
if err != nil {
return newRouterCertsDegradedf("NoRouterCertSecret", "neither the custom secret/%v -n %v or default secret/%v -n %v could be retrieved: %v", c.defaultSecretName, c.secretNamespace, c.customSecretName, c.secretNamespace, err)
}
// Perform a no-op if non-default secret is in use
if secret.GetName() != "v4-0-config-system-router-certs" {
return operatorv1.OperatorCondition{
Type: conditionRouterCertsDegradedType,
Status: operatorv1.ConditionFalse,
Reason: "AsExpected",
}
}
// cert data should exist
data := secret.Data[ingressDomain]
if len(data) == 0 {
return newRouterCertsDegradedf("MissingRouterCertsPEM", "secret/%v.spec.data[%v] -n %v: not found", c.defaultSecretName, ingressDomain, c.secretNamespace)
}
// certificates should be parse-able
certificates, err := crypto.CertsFromPEM(data)
if err != nil {
return newRouterCertsDegradedf("MalformedRouterCertsPEM", "secret/%v.spec.data[%v] -n %v: certificates could not be parsed: %v", c.defaultSecretName, ingressDomain, c.secretNamespace, err)
}
// get default router CA cert cm
cm, err := c.configMapLister.ConfigMaps("openshift-config-managed").Get("default-ingress-cert")
if err != nil {
return newRouterCertsDegradedf("NoDefaultIngressCAConfigMap", "failed to get configMap openshift-config-managed/default-ingress-cert: %v", err)
}
ingressCABundlePEM, ok := cm.Data["ca-bundle.crt"]
if !ok {
return newRouterCertsDegraded("MissingIngressCACerts", "configMap/default-ingress-cert.data[ca-bundle.crt] -n openshift-config-managed: empty")
}
ingressCACerts, err := crypto.CertsFromPEM([]byte(ingressCABundlePEM))
if err != nil {
return newRouterCertsDegradedf("MalformedIngressCACertsPem", "configMap/default-ingress-cert.data[ca-bundle.crt] -n openshift-config-managed: certificates could not be parsed: %v", err)
}
// categorize certificates
verifyOptions := x509.VerifyOptions{}
verifyOptions.DNSName = c.routeName + "." + ingressDomain
verifyOptions.Intermediates = x509.NewCertPool()
verifyOptions.Roots, err = c.systemCertPool()
if err != nil {
klog.Infof("system cert pool not available: %v", err)
verifyOptions.Roots = x509.NewCertPool()
}
// ignore the server cert from the default cert bundle
populateVerifyOptionsFromCertSlice(&verifyOptions, ingressCACerts)
if len(verifyOptions.Roots.Subjects()) == 0 { // the CA certs can also appear in the secret, but by default we should also trust the default ingress CA bundle for the default routes
return newRouterCertsDegradedf("NoRootCARouterCerts", "configMap/default-ingress-cert.data[ca-bundle.crt] -n openshift-config-managed: no root CA certificates found in the CM or system")
}
serverCerts := populateVerifyOptionsFromCertSlice(&verifyOptions, certificates)
if len(serverCerts) == 0 {
return newRouterCertsDegradedf("NoServerCertRouterCerts", "secret/%v.spec.data[%v] -n %v: no server certificates found", c.defaultSecretName, ingressDomain, c.secretNamespace)
}
// verify certificate chain
if err := verifyWithAnyCertificate(serverCerts, verifyOptions); err != nil {
return newRouterCertsDegradedf("InvalidServerCertRouterCerts", "secret/%v.spec.data[%v] -n %v: certificate could not validate route hostname %v: %v", c.defaultSecretName, ingressDomain, c.secretNamespace, verifyOptions.DNSName, err)
}
// we made it this far without a problem
return operatorv1.OperatorCondition{
Type: conditionRouterCertsDegradedType,
Status: operatorv1.ConditionFalse,
Reason: "AsExpected",
}
}
func newRouterCertsDegradedf(reason, message string, args ...interface{}) operatorv1.OperatorCondition {
return newRouterCertsDegraded(reason, fmt.Sprintf(message, args...))
}
func newRouterCertsDegraded(reason, message string) operatorv1.OperatorCondition {
return operatorv1.OperatorCondition{
Type: conditionRouterCertsDegradedType,
Status: operatorv1.ConditionTrue,
Reason: reason,
Message: message,
}
}
func verifyWithAnyCertificate(serverCerts []*x509.Certificate, options x509.VerifyOptions) error {
var err error
for _, certificate := range serverCerts {
_, err = certificate.Verify(options)
if err == nil {
klog.V(4).Infof("cert %s passed verification", certificate.Subject.String())
return nil
}
klog.V(4).Infof("cert %s failed verification: %v", certificate.Subject.String(), err)
}
// no certificate was able to verify dns name, return last error
return err
}
func populateVerifyOptionsFromCertSlice(opts *x509.VerifyOptions, certs []*x509.Certificate) []*x509.Certificate {
serverCerts := []*x509.Certificate{}
for _, certificate := range certs {
switch {
case certificate.IsCA && bytes.Equal(certificate.RawSubject, certificate.RawIssuer):
klog.V(4).Infof("using CA %s as root", certificate.Subject.String())
opts.Roots.AddCert(certificate)
case certificate.IsCA:
klog.V(4).Infof("using CA %s as intermediate", certificate.Subject.String())
opts.Intermediates.AddCert(certificate)
default:
serverCerts = append(serverCerts, certificate)
}
}
return serverCerts
}