diff --git a/bindata/oauth-apiserver/deploy.yaml b/bindata/oauth-apiserver/deploy.yaml index 387a048e59..5235bd2b50 100644 --- a/bindata/oauth-apiserver/deploy.yaml +++ b/bindata/oauth-apiserver/deploy.yaml @@ -114,6 +114,12 @@ spec: name: serving-cert - mountPath: /var/run/secrets/encryption-config name: encryption-config + - mountPath: /var/run/secrets/loopback/apiserver-loopback-client__.crt + name: loopback-cert + subPath: "tls.crt" + - mountPath: /var/run/secrets/loopback/apiserver-loopback-client__.key + name: loopback-cert + subPath: "tls.key" - mountPath: /var/log/oauth-apiserver name: audit-dir livenessProbe: @@ -174,6 +180,9 @@ spec: - hostPath: path: /var/log/oauth-apiserver name: audit-dir + - name: loopback-cert + secret: + secretName: loopback nodeSelector: node-role.kubernetes.io/master: "" tolerations: diff --git a/pkg/controllers/certrotationcontroller/certrotationcontroller.go b/pkg/controllers/certrotationcontroller/certrotationcontroller.go new file mode 100644 index 0000000000..6d11a095d1 --- /dev/null +++ b/pkg/controllers/certrotationcontroller/certrotationcontroller.go @@ -0,0 +1,111 @@ +package certrotationcontroller + +import ( + "context" + "fmt" + "time" + + operatorv1 "github.com/openshift/api/operator/v1" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + + "github.com/openshift/library-go/pkg/controller/factory" + "github.com/openshift/library-go/pkg/operator/certrotation" + "github.com/openshift/library-go/pkg/operator/condition" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/openshift/library-go/pkg/operator/v1helpers" +) + +type OperatorConditionStatusReporter struct { + // Plumbing: + OperatorClient v1helpers.OperatorClient +} + +func (s *OperatorConditionStatusReporter) 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 = "CertificateRotationError" + newCondition.Message = syncErr.Error() + } + _, updated, updateErr := v1helpers.UpdateStatus(ctx, s.OperatorClient, v1helpers.UpdateConditionFn(newCondition)) + return updated, updateErr +} + +type CertRotationController struct { + certRotators []factory.Controller +} + +func NewCertRotationController( + secretsGetter corev1client.SecretsGetter, + configMapsGetter corev1client.ConfigMapsGetter, + operatorClient v1helpers.OperatorClient, + kubeInformersForNamespaces v1helpers.KubeInformersForNamespaces, + eventRecorder events.Recorder, + day time.Duration, +) (*CertRotationController, error) { + ret := &CertRotationController{} + + targetNS := "openshift-oauth-apiserver" + + certRotator := certrotation.NewCertRotationController( + "OAuthLoopbackCert", + certrotation.RotatedSigningCASecret{ + Namespace: targetNS, + Name: "loopback-signer", + AdditionalAnnotations: certrotation.AdditionalAnnotations{ + JiraComponent: "oauth-apiserver", + }, + Validity: 60 * day, + Refresh: 30 * day, + RefreshOnlyWhenExpired: false, + Informer: kubeInformersForNamespaces.InformersFor(targetNS).Core().V1().Secrets(), + Lister: kubeInformersForNamespaces.InformersFor(targetNS).Core().V1().Secrets().Lister(), + Client: secretsGetter, + EventRecorder: eventRecorder, + }, + certrotation.CABundleConfigMap{ + Namespace: targetNS, + Name: "loopback-ca", + AdditionalAnnotations: certrotation.AdditionalAnnotations{ + JiraComponent: "oauth-apiserver", + }, + Informer: kubeInformersForNamespaces.InformersFor(targetNS).Core().V1().ConfigMaps(), + Lister: kubeInformersForNamespaces.InformersFor(targetNS).Core().V1().ConfigMaps().Lister(), + Client: configMapsGetter, + EventRecorder: eventRecorder, + }, + certrotation.RotatedSelfSignedCertKeySecret{ + Namespace: targetNS, + Name: "loopback", + AdditionalAnnotations: certrotation.AdditionalAnnotations{ + JiraComponent: "oauth-apiserver", + }, + Validity: 30 * day, + Refresh: 15 * day, + RefreshOnlyWhenExpired: false, + CertCreator: &certrotation.ServingRotation{ + Hostnames: func() []string { return []string{"apiserver-loopback-client"} }, + }, + Informer: kubeInformersForNamespaces.InformersFor(targetNS).Core().V1().Secrets(), + Lister: kubeInformersForNamespaces.InformersFor(targetNS).Core().V1().Secrets().Lister(), + Client: secretsGetter, + EventRecorder: eventRecorder, + }, + eventRecorder, + &OperatorConditionStatusReporter{OperatorClient: operatorClient}, + ) + + ret.certRotators = append(ret.certRotators, certRotator) + + return ret, nil +} + +func (c *CertRotationController) Run(ctx context.Context, workers int) { + syncCtx := context.WithValue(ctx, certrotation.RunOnceContextKey, false) + for _, certRotator := range c.certRotators { + go certRotator.Run(syncCtx, workers) + } +} diff --git a/pkg/operator/starter.go b/pkg/operator/starter.go index e5f4e3b85b..4a4dafe217 100644 --- a/pkg/operator/starter.go +++ b/pkg/operator/starter.go @@ -16,6 +16,7 @@ import ( routev1 "github.com/openshift/api/route/v1" applyoperatorv1 "github.com/openshift/client-go/operator/applyconfigurations/operator/v1" "github.com/openshift/cluster-authentication-operator/bindata" + "github.com/openshift/cluster-authentication-operator/pkg/controllers/certrotationcontroller" "github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation/configobservercontroller" componentroutesecretsync "github.com/openshift/cluster-authentication-operator/pkg/controllers/customroute" "github.com/openshift/cluster-authentication-operator/pkg/controllers/deployment" @@ -652,6 +653,18 @@ func prepareOauthAPIServerOperator( eventRecorder, ) + certRotationController, err := certrotationcontroller.NewCertRotationController( + v1helpers.CachedSecretGetter(authOperatorInput.kubeClient.CoreV1(), informerFactories.kubeInformersForNamespaces), + v1helpers.CachedConfigMapGetter(authOperatorInput.kubeClient.CoreV1(), informerFactories.kubeInformersForNamespaces), + authOperatorInput.authenticationOperatorClient, + informerFactories.kubeInformersForNamespaces, + authOperatorInput.eventRecorder, + time.Hour*24, + ) + if err != nil { + return nil, nil, err + } + runOnceFns := []libraryapplyconfiguration.NamedRunOnce{ libraryapplyconfiguration.AdaptSyncFn(authOperatorInput.eventRecorder, "TODO-other-configObserver", configObserver.Sync), libraryapplyconfiguration.AdaptSyncFn(authOperatorInput.eventRecorder, "TODO-authenticatorCertRequester", authenticatorCertRequester.Sync), @@ -668,6 +681,7 @@ func prepareOauthAPIServerOperator( libraryapplyconfiguration.AdaptRunFn(webhookAuthController.Run), libraryapplyconfiguration.AdaptRunFn(webhookCertsApprover.Run), libraryapplyconfiguration.AdaptRunFn(func(ctx context.Context, _ int) { apiServerControllers.Run(ctx) }), + libraryapplyconfiguration.AdaptRunFn(certRotationController.Run), } return runOnceFns, runFns, nil