/
oidcsetup.go
291 lines (250 loc) · 10.8 KB
/
oidcsetup.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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
package oidcsetup
import (
"context"
"fmt"
"time"
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
appsv1informers "k8s.io/client-go/informers/apps/v1"
corev1informers "k8s.io/client-go/informers/core/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
appsv1listers "k8s.io/client-go/listers/apps/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/klog/v2"
configv1 "github.com/openshift/api/config/v1"
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"
operatorv1informers "github.com/openshift/client-go/operator/informers/externalversions/operator/v1"
operatorv1listers "github.com/openshift/client-go/operator/listers/operator/v1"
"github.com/openshift/console-operator/pkg/api"
"github.com/openshift/console-operator/pkg/console/status"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
"github.com/openshift/library-go/pkg/operator/v1helpers"
authnsub "github.com/openshift/console-operator/pkg/console/subresource/authentication"
deploymentsub "github.com/openshift/console-operator/pkg/console/subresource/deployment"
utilsub "github.com/openshift/console-operator/pkg/console/subresource/util"
)
// oidcSetupController:
//
// writes:
// - authentication.config.openshift.io/cluster .status.oidcClients:
// - componentName=console
// - componentNamespace=openshift-console
// - currentOIDCClients
// - conditions:
// - Available
// - Progressing
// - Degraded
// - consoles.operator.openshift.io/cluster .status.conditions:
// - type=OIDCClientConfigProgressing
// - type=OIDCClientConfigDegraded
// - type=AuthStatusHandlerProgressing
// - type=AuthStatusHandlerDegraded
type oidcSetupController struct {
operatorClient v1helpers.OperatorClient
configMapClient corev1client.ConfigMapsGetter
authnLister configv1listers.AuthenticationLister
consoleOperatorLister operatorv1listers.ConsoleLister
configConfigMapLister corev1listers.ConfigMapLister
targetNSSecretsLister corev1listers.SecretLister
targetNSConfigMapLister corev1listers.ConfigMapLister
targetNSDeploymentsLister appsv1listers.DeploymentLister
externalOIDCFeatureEnabled bool
authStatusHandler *status.AuthStatusHandler
}
func NewOIDCSetupController(
operatorClient v1helpers.OperatorClient,
configMapClient corev1client.ConfigMapsGetter,
authnInformer configv1informers.AuthenticationInformer,
authenticationClient configv1client.AuthenticationInterface,
consoleOperatorInformer operatorv1informers.ConsoleInformer,
configConfigMapInformer corev1informers.ConfigMapInformer,
targetNSsecretsInformer corev1informers.SecretInformer,
targetNSConfigMapInformer corev1informers.ConfigMapInformer,
targetNSDeploymentsInformer appsv1informers.DeploymentInformer,
externalOIDCFeatureEnabled bool,
recorder events.Recorder,
) factory.Controller {
c := &oidcSetupController{
operatorClient: operatorClient,
configMapClient: configMapClient,
authnLister: authnInformer.Lister(),
consoleOperatorLister: consoleOperatorInformer.Lister(),
configConfigMapLister: configConfigMapInformer.Lister(),
targetNSSecretsLister: targetNSsecretsInformer.Lister(),
targetNSDeploymentsLister: targetNSDeploymentsInformer.Lister(),
targetNSConfigMapLister: targetNSConfigMapInformer.Lister(),
externalOIDCFeatureEnabled: externalOIDCFeatureEnabled,
authStatusHandler: status.NewAuthStatusHandler(authenticationClient, api.OpenShiftConsoleName, api.TargetNamespace, api.OpenShiftConsoleOperator),
}
return factory.New().
WithSync(c.sync).
ResyncEvery(wait.Jitter(time.Minute, 1.0)).
WithInformers(
authnInformer.Informer(),
configConfigMapInformer.Informer(),
consoleOperatorInformer.Informer(),
targetNSsecretsInformer.Informer(),
targetNSDeploymentsInformer.Informer(),
targetNSConfigMapInformer.Informer(),
).
ToController("OIDCSetupController", recorder.WithComponentSuffix("oidc-setup-controller"))
}
func (c *oidcSetupController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
statusHandler := status.NewStatusHandler(c.operatorClient)
if shouldSync, err := c.handleManaged(); err != nil {
return err
} else if !shouldSync {
return nil
}
// we assume API validation won't allow authentication/cluster 'spec.type=OIDC'
// if the OIDC feature gate is not enabled
if !c.externalOIDCFeatureEnabled {
// reset all conditions set by this controller
statusHandler.AddConditions(status.HandleProgressingOrDegraded("OIDCClientConfig", "", nil))
statusHandler.AddConditions(status.HandleProgressingOrDegraded("AuthStatusHandler", "", nil))
return statusHandler.FlushAndReturn(nil)
}
authnConfig, err := c.authnLister.Get(api.ConfigResourceName)
if err != nil {
return err
}
operatorConfig, err := c.consoleOperatorLister.Get("cluster")
if err != nil {
return err
}
if authnConfig.Spec.Type != configv1.AuthenticationTypeOIDC {
applyErr := c.authStatusHandler.Apply(ctx, authnConfig)
statusHandler.AddConditions(status.HandleProgressingOrDegraded("AuthStatusHandler", "FailedApply", applyErr))
// reset the other condition set by this controller
statusHandler.AddConditions(status.HandleProgressingOrDegraded("OIDCClientConfig", "", nil))
return statusHandler.FlushAndReturn(applyErr)
}
// we need to keep track of errors during the sync so that we can requeue
// if any occur
var errs []error
syncErr := c.syncAuthTypeOIDC(ctx, authnConfig, operatorConfig, syncCtx.Recorder())
statusHandler.AddConditions(
status.HandleProgressingOrDegraded(
"OIDCClientConfig", "OIDCConfigSyncFailed",
syncErr,
),
)
if syncErr != nil {
errs = append(errs, syncErr)
}
applyErr := c.authStatusHandler.Apply(ctx, authnConfig)
statusHandler.AddConditions(status.HandleProgressingOrDegraded("AuthStatusHandler", "FailedApply", applyErr))
if applyErr != nil {
errs = append(errs, applyErr)
}
if len(errs) > 0 {
return statusHandler.FlushAndReturn(factory.SyntheticRequeueError)
}
return statusHandler.FlushAndReturn(nil)
}
func (c *oidcSetupController) syncAuthTypeOIDC(ctx context.Context, authnConfig *configv1.Authentication, operatorConfig *operatorv1.Console, recorder events.Recorder) error {
oidcProvider, clientConfig := authnsub.GetOIDCClientConfig(authnConfig, api.TargetNamespace, api.OpenShiftConsoleName)
if clientConfig == nil {
c.authStatusHandler.WithCurrentOIDCClient("")
c.authStatusHandler.Unavailable("OIDCClientConfig", "no OIDC client found")
return nil
}
if len(clientConfig.ClientID) == 0 {
return fmt.Errorf("no ID set on console's OIDC client")
}
c.authStatusHandler.WithCurrentOIDCClient(clientConfig.ClientID)
if len(clientConfig.ClientSecret.Name) == 0 {
c.authStatusHandler.Degraded("OIDCClientMissingSecret", "no client secret in the OIDC client config")
return nil
}
clientSecret, err := c.targetNSSecretsLister.Secrets(api.TargetNamespace).Get("console-oauth-config")
if err != nil {
c.authStatusHandler.Degraded("OIDCClientSecretGet", err.Error())
return err
}
if caCMName := oidcProvider.Issuer.CertificateAuthority.Name; len(caCMName) > 0 {
caCM, err := c.configConfigMapLister.ConfigMaps(api.OpenShiftConfigNamespace).Get(caCMName)
if err != nil {
return fmt.Errorf("failed to get the CA configMap %q configured for the OIDC provider %q: %w", caCMName, oidcProvider.Name, err)
}
_, _, err = resourceapply.SyncPartialConfigMap(ctx,
c.configMapClient,
recorder,
caCM.Namespace, caCM.Name,
api.TargetNamespace, caCM.Name,
sets.NewString("ca-bundle.crt"),
[]metav1.OwnerReference{*utilsub.OwnerRefFrom(operatorConfig)})
if err != nil {
return fmt.Errorf("failed to sync the provider's CA configMap: %w", err)
}
}
if valid, msg, err := c.checkClientConfigStatus(authnConfig, clientSecret); err != nil {
c.authStatusHandler.Degraded("DeploymentOIDCConfig", err.Error())
return err
} else if !valid {
c.authStatusHandler.Progressing("DeploymentOIDCConfig", msg)
return nil
}
c.authStatusHandler.Available("OIDCConfigAvailable", "")
return nil
}
// checkClientConfigStatus checks whether the current client configuration is being currently in use,
// by looking at the deployment status. It checks whether the deployment is available and updated,
// and also whether the resource versions for the oauth secret and server CA trust configmap match
// the deployment.
func (c *oidcSetupController) checkClientConfigStatus(authnConfig *configv1.Authentication, clientSecret *corev1.Secret) (bool, string, error) {
depl, err := c.targetNSDeploymentsLister.Deployments(api.OpenShiftConsoleNamespace).Get(api.OpenShiftConsoleDeploymentName)
if err != nil {
return false, "", err
}
deplAvailableUpdated := deploymentsub.IsAvailableAndUpdated(depl)
if !deplAvailableUpdated {
return false, "deployment unavailable or outdated", nil
}
if clientSecret.GetResourceVersion() != depl.ObjectMeta.Annotations["console.openshift.io/oauth-secret-version"] {
return false, "client secret version not up to date in current deployment", nil
}
if len(authnConfig.Spec.OIDCProviders) > 0 {
serverCAConfigName := authnConfig.Spec.OIDCProviders[0].Issuer.CertificateAuthority.Name
if len(serverCAConfigName) == 0 {
return deplAvailableUpdated, "", nil
}
serverCAConfig, err := c.targetNSConfigMapLister.ConfigMaps(api.OpenShiftConsoleNamespace).Get(serverCAConfigName)
if err != nil {
return false, "", err
}
if serverCAConfig.GetResourceVersion() != depl.ObjectMeta.Annotations["console.openshift.io/authn-ca-trust-config-version"] {
return false, "OIDC provider CA version not up to date in current deployment", nil
}
}
return deplAvailableUpdated, "", nil
}
// handleStatus returns whether sync should happen and any error encountering
// determining the operator's management state
// TODO: extract this logic to where it can be used for all controllers
func (c *oidcSetupController) handleManaged() (bool, error) {
operatorSpec, _, _, err := c.operatorClient.GetOperatorState()
if err != nil {
return false, fmt.Errorf("failed to retrieve operator config: %w", err)
}
switch managementState := operatorSpec.ManagementState; managementState {
case operatorv1.Managed:
klog.V(4).Infoln("console is in a managed state.")
return true, nil
case operatorv1.Unmanaged:
klog.V(4).Infoln("console is in an unmanaged state.")
return false, nil
case operatorv1.Removed:
klog.V(4).Infoln("console has been removed.")
return false, nil
default:
return false, fmt.Errorf("console is in an unknown state: %v", managementState)
}
}