/
client_cert_rotation_controller.go
162 lines (141 loc) · 5.64 KB
/
client_cert_rotation_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
package certrotation
import (
"context"
"fmt"
"time"
operatorv1 "github.com/openshift/api/operator/v1"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/condition"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/v1helpers"
)
const (
// CertificateNotBeforeAnnotation contains the certificate expiration date in RFC3339 format.
CertificateNotBeforeAnnotation = "auth.openshift.io/certificate-not-before"
// CertificateNotAfterAnnotation contains the certificate expiration date in RFC3339 format.
CertificateNotAfterAnnotation = "auth.openshift.io/certificate-not-after"
// CertificateIssuer contains the common name of the certificate that signed another certificate.
CertificateIssuer = "auth.openshift.io/certificate-issuer"
// CertificateHostnames contains the hostnames used by a signer.
CertificateHostnames = "auth.openshift.io/certificate-hostnames"
// RunOnceContextKey is a context value key that can be used to call the controller Sync() and make it only run the syncWorker once and report error.
RunOnceContextKey = "cert-rotation-controller.openshift.io/run-once"
)
// StatusReporter knows how to report the status of cert rotation
type StatusReporter interface {
Report(ctx context.Context, controllerName string, syncErr error) (updated bool, updateErr error)
}
var _ StatusReporter = (*StaticPodConditionStatusReporter)(nil)
type StaticPodConditionStatusReporter struct {
// Plumbing:
OperatorClient v1helpers.StaticPodOperatorClient
}
func (s *StaticPodConditionStatusReporter) Report(ctx context.Context, controllerName string, syncErr error) (bool, error) {
newCondition := operatorv1.OperatorCondition{
Type: fmt.Sprintf(condition.CertRotationDegradedConditionTypeFmt, controllerName),
Status: operatorv1.ConditionFalse,
}
if syncErr != nil {
newCondition.Status = operatorv1.ConditionTrue
newCondition.Reason = "RotationError"
newCondition.Message = syncErr.Error()
}
_, updated, updateErr := v1helpers.UpdateStaticPodStatus(ctx, s.OperatorClient, v1helpers.UpdateStaticPodConditionFn(newCondition))
return updated, updateErr
}
// CertRotationController does:
//
// 1) continuously create a self-signed signing CA (via RotatedSigningCASecret) and store it in a secret.
// 2) maintain a CA bundle ConfigMap with all not yet expired CA certs.
// 3) continuously create a target cert and key signed by the latest signing CA and store it in a secret.
type CertRotationController struct {
// controller name
Name string
// RotatedSigningCASecret rotates a self-signed signing CA stored in a secret.
RotatedSigningCASecret RotatedSigningCASecret
// CABundleConfigMap maintains a CA bundle config map, by adding new CA certs coming from rotatedSigningCASecret, and by removing expired old ones.
CABundleConfigMap CABundleConfigMap
// RotatedSelfSignedCertKeySecret rotates a key and cert signed by a signing CA and stores it in a secret.
RotatedSelfSignedCertKeySecret RotatedSelfSignedCertKeySecret
// Plumbing:
StatusReporter StatusReporter
}
func NewCertRotationController(
name string,
rotatedSigningCASecret RotatedSigningCASecret,
caBundleConfigMap CABundleConfigMap,
rotatedSelfSignedCertKeySecret RotatedSelfSignedCertKeySecret,
recorder events.Recorder,
reporter StatusReporter,
) factory.Controller {
c := &CertRotationController{
Name: name,
RotatedSigningCASecret: rotatedSigningCASecret,
CABundleConfigMap: caBundleConfigMap,
RotatedSelfSignedCertKeySecret: rotatedSelfSignedCertKeySecret,
StatusReporter: reporter,
}
return factory.New().
ResyncEvery(time.Minute).
WithSync(c.Sync).
WithInformers(
rotatedSigningCASecret.Informer.Informer(),
caBundleConfigMap.Informer.Informer(),
rotatedSelfSignedCertKeySecret.Informer.Informer(),
).
WithPostStartHooks(
c.targetCertRecheckerPostRunHook,
).
ToController("CertRotationController", recorder.WithComponentSuffix("cert-rotation-controller").WithComponentSuffix(name))
}
func (c CertRotationController) Sync(ctx context.Context, syncCtx factory.SyncContext) error {
syncErr := c.SyncWorker(ctx)
// running this function with RunOnceContextKey value context will make this "run-once" without updating status.
isRunOnce, ok := ctx.Value(RunOnceContextKey).(bool)
if ok && isRunOnce {
return syncErr
}
updated, updateErr := c.StatusReporter.Report(ctx, c.Name, syncErr)
if updateErr != nil {
return updateErr
}
if updated && syncErr != nil {
syncCtx.Recorder().Warningf("RotationError", syncErr.Error())
}
return syncErr
}
func (c CertRotationController) SyncWorker(ctx context.Context) error {
signingCertKeyPair, _, err := c.RotatedSigningCASecret.EnsureSigningCertKeyPair(ctx)
if err != nil {
return err
}
cabundleCerts, err := c.CABundleConfigMap.EnsureConfigMapCABundle(ctx, signingCertKeyPair)
if err != nil {
return err
}
if _, err := c.RotatedSelfSignedCertKeySecret.EnsureTargetCertKeyPair(ctx, signingCertKeyPair, cabundleCerts); err != nil {
return err
}
return nil
}
func (c CertRotationController) targetCertRecheckerPostRunHook(ctx context.Context, syncCtx factory.SyncContext) error {
// If we have a need to force rechecking the cert, use this channel to do it.
refresher, ok := c.RotatedSelfSignedCertKeySecret.CertCreator.(TargetCertRechecker)
if !ok {
return nil
}
targetRefresh := refresher.RecheckChannel()
go wait.Until(func() {
for {
select {
case <-targetRefresh:
syncCtx.Queue().Add(factory.DefaultQueueKey)
case <-ctx.Done():
return
}
}
}, time.Minute, ctx.Done())
<-ctx.Done()
return nil
}