Skip to content

Commit d40b027

Browse files
committed
Add component-scoped proxy to all controllers
Wire the component-scoped proxy into every affected controller: Config observer: builds a proxy-aware HTTP transport for OIDC discovery and password grant checks. The Listers struct gains OperatorAuthLister and FeatureGateAccessor. The convertIdentityProviders signature changes from a ConfigMap lister to a transportForCABuilderFunc, decoupling transport construction from the IDP conversion logic. Adds an ObserveProxyTrustedCA observer that injects the proxy CA bundle path into the observed config when a component proxy with a trustedCA is active. Deployment controller: resolves the component proxy, injects HTTP_PROXY/HTTPS_PROXY/NO_PROXY env vars into the OAuth Server pod, syncs the trustedCA ConfigMap from openshift-config to openshift-authentication, and adds a volume/mount with Optional=true for CA hot-reload without redeployment. Proxy validation controller: validates route reachability through the component proxy. Tests IdP endpoint connectivity and emits IdPEndpointUnreachable Warning events for transient failures (does not set Degraded). Uses hash-based change detection to avoid redundant validation. Endpoint accessible controller: gains an optional proxy function for route health checks. Only the route check controller gets the proxy because it connects to the external route hostname; service and endpoint checks connect to cluster-internal addresses and remain proxy-free. Custom route controller: resolves the component proxy via ResolveProxy for the route availability health check. Loads the proxy trusted CA via LoadCAData and appends it to the root CA pool. Fixes the routeAvailablity typo (now routeAvailability). Operator startup: passes operatorAuthLister, featureGateAccessor, and operatorAuthInformer to all affected controllers.
1 parent 8cf3752 commit d40b027

18 files changed

Lines changed: 1131 additions & 288 deletions

pkg/controllers/configobservation/configobservercontroller/observe_config_controller.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"k8s.io/client-go/tools/cache"
66

77
configinformers "github.com/openshift/client-go/config/informers/externalversions"
8+
operatorv1informers "github.com/openshift/client-go/operator/informers/externalversions/operator/v1"
89
"github.com/openshift/library-go/pkg/controller/factory"
910
"github.com/openshift/library-go/pkg/operator/configobserver"
1011
"github.com/openshift/library-go/pkg/operator/configobserver/apiserver"
12+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
1113
configobserveroauth "github.com/openshift/library-go/pkg/operator/configobserver/oauth"
1214
"github.com/openshift/library-go/pkg/operator/events"
1315
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
@@ -27,6 +29,8 @@ func NewConfigObserver(
2729
resourceSyncer resourcesynccontroller.ResourceSyncer,
2830
enabledClusterCapabilities sets.String,
2931
eventRecorder events.Recorder,
32+
operatorAuthInformer operatorv1informers.AuthenticationInformer,
33+
featureGateAccessor featuregates.FeatureGateAccess,
3034
) factory.Controller {
3135
interestingNamespaces := []string{
3236
"openshift-authentication",
@@ -73,13 +77,17 @@ func NewConfigObserver(
7377
oauth.ObserveTemplates,
7478
oauth.ObserveTokenConfig,
7579
oauth.ObserveAudit,
80+
oauth.ObserveComponentProxyTrustedCA,
7681
configobserveroauth.ObserveAccessTokenInactivityTimeout,
7782
routersecret.ObserveRouterSecret,
7883
} {
7984
oauthServerObservers = append(oauthServerObservers,
8085
configobserver.WithPrefix(o, configobservation.OAuthServerConfigPrefix))
8186
}
8287

88+
preRunCacheSynced = append(preRunCacheSynced, operatorAuthInformer.Informer().HasSynced)
89+
informers = append(informers, operatorAuthInformer.Informer())
90+
8391
listers := configobservation.Listers{
8492
ConfigMapLister: kubeInformersForNamespaces.ConfigMapLister(),
8593
SecretsLister: kubeInformersForNamespaces.SecretLister(),
@@ -91,6 +99,9 @@ func NewConfigObserver(
9199
OAuthLister_: configInformer.Config().V1().OAuths().Lister(),
92100
ResourceSync: resourceSyncer,
93101
PreRunCachesSynced: preRunCacheSynced,
102+
103+
OperatorAuthLister: operatorAuthInformer.Lister(),
104+
FeatureGateAccessor: featureGateAccessor,
94105
}
95106

96107
// Check if the Console capability is enabled on the cluster and sync and add its informer, lister, and config observer

pkg/controllers/configobservation/interfaces.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"k8s.io/client-go/tools/cache"
66

77
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
8+
operatorv1listers "github.com/openshift/client-go/operator/listers/operator/v1"
89
"github.com/openshift/library-go/pkg/operator/configobserver"
10+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
911
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
1012
)
1113

@@ -26,6 +28,9 @@ type Listers struct {
2628
OAuthLister_ configlistersv1.OAuthLister
2729
IngressLister configlistersv1.IngressLister
2830

31+
OperatorAuthLister operatorv1listers.AuthenticationLister
32+
FeatureGateAccessor featuregates.FeatureGateAccess
33+
2934
ResourceSync resourcesynccontroller.ResourceSyncer
3035
PreRunCachesSynced []cache.InformerSynced
3136
}

pkg/controllers/configobservation/oauth/idp_conversions.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ import (
1919
osinv1 "github.com/openshift/api/osin/v1"
2020

2121
"github.com/openshift/cluster-authentication-operator/pkg/operator/datasync"
22-
"github.com/openshift/cluster-authentication-operator/pkg/transport"
2322
)
2423

24+
// transportForCABuilderFunc builds an http.RoundTripper for a given CA ConfigMap
25+
// reference. The cmLister and any proxy settings are captured in the closure.
26+
type transportForCABuilderFunc func(caConfigMapName, key string) (http.RoundTripper, error)
27+
2528
// field names are used to uniquely identify a secret or config map reference
2629
// within a given identity provider. thus the same IDP cannot use the same field
2730
// more than once. ex: if an idp had two CA fields, it would need to use something
@@ -54,16 +57,16 @@ type idpData struct {
5457
}
5558

5659
func convertIdentityProviders(
57-
cmLister corelistersv1.ConfigMapLister,
5860
secretsLister corelistersv1.SecretLister,
5961
identityProviders []configv1.IdentityProvider,
62+
buildTransport transportForCABuilderFunc,
6063
) ([]interface{}, *datasync.ConfigSyncData, []error) {
6164
converted := []osinv1.IdentityProvider{}
6265
syncData := datasync.NewConfigSyncData()
6366
errs := []error{}
6467

6568
for i, idp := range defaultIDPMappingMethods(identityProviders) {
66-
data, err := convertProviderConfigToIDPData(cmLister, secretsLister, &idp.IdentityProviderConfig, syncData, i)
69+
data, err := convertProviderConfigToIDPData(secretsLister, &idp.IdentityProviderConfig, syncData, i, buildTransport)
6770
if err != nil {
6871
errs = append(errs, fmt.Errorf("failed to apply IDP %s config: %v", idp.Name, err))
6972
continue
@@ -111,11 +114,11 @@ func defaultIDPMappingMethods(identityProviders []configv1.IdentityProvider) []c
111114
}
112115

113116
func convertProviderConfigToIDPData(
114-
cmLister corelistersv1.ConfigMapLister,
115117
secretsLister corelistersv1.SecretLister,
116118
providerConfig *configv1.IdentityProviderConfig,
117119
syncData *datasync.ConfigSyncData,
118120
i int,
121+
buildTransport transportForCABuilderFunc,
119122
) (*idpData, error) {
120123
const missingProviderFmt string = "type %s was specified, but its configuration is missing"
121124

@@ -241,7 +244,7 @@ func convertProviderConfigToIDPData(
241244
return nil, fmt.Errorf(missingProviderFmt, providerConfig.Type)
242245
}
243246

244-
urls, err := discoverOpenIDURLs(cmLister, openIDConfig.Issuer, corev1.ServiceAccountRootCAKey, openIDConfig.CA)
247+
urls, err := discoverOpenIDURLs(openIDConfig.Issuer, corev1.ServiceAccountRootCAKey, openIDConfig.CA, buildTransport)
245248
if err != nil {
246249
return nil, err
247250
}
@@ -272,12 +275,12 @@ func convertProviderConfigToIDPData(
272275
// challenge-redirecting IdPs to be configured with OIDC so it is safe
273276
// to allow challenge-issuing flow if it's available on the OIDC side
274277
challengeFlowsAllowed, err := checkOIDCPasswordGrantFlow(
275-
cmLister,
276278
secretsLister,
277279
urls.Token,
278280
openIDConfig.ClientID,
279281
openIDConfig.CA,
280282
openIDConfig.ClientSecret,
283+
buildTransport,
281284
)
282285
if err != nil {
283286
return nil, fmt.Errorf("error attempting password grant flow: %v", err)
@@ -312,7 +315,7 @@ func convertProviderConfigToIDPData(
312315

313316
// discoverOpenIDURLs retrieves basic information about an OIDC server with hostname
314317
// given by the `issuer` argument
315-
func discoverOpenIDURLs(cmLister corelistersv1.ConfigMapLister, issuer, key string, ca configv1.ConfigMapNameReference) (*osinv1.OpenIDURLs, error) {
318+
func discoverOpenIDURLs(issuer, key string, ca configv1.ConfigMapNameReference, buildTransport transportForCABuilderFunc) (*osinv1.OpenIDURLs, error) {
316319
issuer = strings.TrimRight(issuer, "/") // TODO make impossible via validation and remove
317320

318321
wellKnown := issuer + "/.well-known/openid-configuration"
@@ -321,7 +324,7 @@ func discoverOpenIDURLs(cmLister corelistersv1.ConfigMapLister, issuer, key stri
321324
return nil, err
322325
}
323326

324-
rt, err := transport.TransportForCARef(cmLister, ca.Name, key)
327+
rt, err := buildTransport(ca.Name, key)
325328
if err != nil {
326329
return nil, err
327330
}
@@ -371,11 +374,11 @@ func discoverOpenIDURLs(cmLister corelistersv1.ConfigMapLister, issuer, key stri
371374
}
372375

373376
func checkOIDCPasswordGrantFlow(
374-
cmLister corelistersv1.ConfigMapLister,
375377
secretsLister corelistersv1.SecretLister,
376378
tokenURL, clientID string,
377379
caRererence configv1.ConfigMapNameReference,
378380
clientSecretReference configv1.SecretNameReference,
381+
buildTransport transportForCABuilderFunc,
379382
) (bool, error) {
380383
secret, err := secretsLister.Secrets("openshift-config").Get(clientSecretReference.Name)
381384
if err != nil {
@@ -394,7 +397,7 @@ func checkOIDCPasswordGrantFlow(
394397
return false, fmt.Errorf("the referenced secret does not contain a value for the 'clientSecret' key")
395398
}
396399

397-
transport, err := transport.TransportForCARef(cmLister, caRererence.Name, corev1.ServiceAccountRootCAKey)
400+
rt, err := buildTransport(caRererence.Name, corev1.ServiceAccountRootCAKey)
398401
if err != nil {
399402
return false, fmt.Errorf("couldn't get a transport for the referenced CA: %v", err)
400403
}
@@ -417,7 +420,7 @@ func checkOIDCPasswordGrantFlow(
417420
// explicitly set Accept to 'application/json' as that's the expected deserializable output
418421
req.Header.Set("Accept", "application/json")
419422

420-
client := &http.Client{Transport: transport}
423+
client := &http.Client{Transport: rt}
421424
resp, err := client.Do(req)
422425
if err != nil {
423426
return false, err

pkg/controllers/configobservation/oauth/idp_conversions_test.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
configv1 "github.com/openshift/api/config/v1"
2424
osinv1 "github.com/openshift/api/osin/v1"
2525
"github.com/openshift/cluster-authentication-operator/pkg/operator/datasync"
26+
"github.com/openshift/cluster-authentication-operator/pkg/transport"
2627
"github.com/openshift/library-go/pkg/crypto"
2728
)
2829

@@ -210,7 +211,7 @@ func Test_convertProviderConfigToIDPData(t *testing.T) {
210211
tt.providerConfig.OpenID.Issuer = server.URL
211212
}
212213

213-
got, err := convertProviderConfigToIDPData(cmLister, secretLister, tt.providerConfig, syncData, 0)
214+
got, err := convertProviderConfigToIDPData(secretLister, tt.providerConfig, syncData, 0, newTestTransportBuilder(cmLister))
214215
if (err != nil) != tt.wantErr {
215216
t.Errorf("convertProviderConfigToIDPData() error = %v, wantErr %v", err, tt.wantErr)
216217
return
@@ -242,6 +243,16 @@ func Test_convertProviderConfigToIDPData(t *testing.T) {
242243
}
243244
}
244245

246+
func newTestTransportBuilder(cmLister corelistersv1.ConfigMapLister) transportForCABuilderFunc {
247+
return func(caConfigMapName, caConfigMapKey string) (http.RoundTripper, error) {
248+
var caRefs []transport.CAReference
249+
if len(caConfigMapName) > 0 {
250+
caRefs = append(caRefs, transport.CAReference{ConfigMapName: caConfigMapName, ConfigMapKey: caConfigMapKey})
251+
}
252+
return transport.TransportForCARef(cmLister, caRefs, "", "", "")
253+
}
254+
}
255+
245256
func newTestHTTPSServer(certPEM, keyPEM []byte, content string) (*httptest.Server, error) {
246257
// use a byte slice reference to replace with a valid content with replaced
247258
// server URLs once the server is started
@@ -304,16 +315,17 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
304315
require.NoError(t, indexer.Add(secret))
305316
cmLister := corelistersv1.NewConfigMapLister(indexer)
306317
secretLister := corelistersv1.NewSecretLister(indexer)
318+
buildTransport := newTestTransportBuilder(cmLister)
307319

308320
t.Run("5xx responses are not cached", func(t *testing.T) {
309321
shouldError = true
310322
result, err := checkOIDCPasswordGrantFlow(
311-
cmLister,
312323
secretLister,
313324
server.URL+"/token",
314325
"test-client",
315326
configv1.ConfigMapNameReference{Name: ""},
316327
configv1.SecretNameReference{Name: "test-secret"},
328+
buildTransport,
317329
)
318330

319331
require.NoError(t, err)
@@ -325,12 +337,12 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
325337
responseContent = `{"error": "invalid_grant"}`
326338
shouldError = false
327339
result, err := checkOIDCPasswordGrantFlow(
328-
cmLister,
329340
secretLister,
330341
server.URL+"/token",
331342
"test-client",
332343
configv1.ConfigMapNameReference{Name: ""},
333344
configv1.SecretNameReference{Name: "test-secret"},
345+
buildTransport,
334346
)
335347

336348
require.NoError(t, err)
@@ -344,12 +356,12 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
344356

345357
shouldError = true
346358
res1, err := checkOIDCPasswordGrantFlow(
347-
cmLister,
348359
secretLister,
349360
server.URL+"/token",
350361
"test-client",
351362
configv1.ConfigMapNameReference{Name: ""},
352363
configv1.SecretNameReference{Name: "test-secret"},
364+
buildTransport,
353365
)
354366
require.NoError(t, err)
355367
require.False(t, res1)
@@ -359,12 +371,12 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
359371
responseContent = `{"error": "invalid_grant"}`
360372
shouldError = false
361373
res2, err := checkOIDCPasswordGrantFlow(
362-
cmLister,
363374
secretLister,
364375
server.URL+"/token",
365376
"test-client",
366377
configv1.ConfigMapNameReference{Name: ""},
367378
configv1.SecretNameReference{Name: "test-secret"},
379+
buildTransport,
368380
)
369381
require.NoError(t, err)
370382
require.True(t, res2)

pkg/controllers/configobservation/oauth/observe_idps.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package oauth
22

33
import (
4+
"net/http"
5+
46
"k8s.io/klog/v2"
57

68
"k8s.io/apimachinery/pkg/api/equality"
@@ -10,8 +12,10 @@ import (
1012
"github.com/openshift/library-go/pkg/operator/configobserver"
1113
"github.com/openshift/library-go/pkg/operator/events"
1214

15+
"github.com/openshift/cluster-authentication-operator/pkg/controllers/common"
1316
"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation"
1417
"github.com/openshift/cluster-authentication-operator/pkg/operator/datasync"
18+
"github.com/openshift/cluster-authentication-operator/pkg/transport"
1519
)
1620

1721
var identityProvidersMounts = []string{"volumesToMount", "identityProviders"}
@@ -50,9 +54,14 @@ func ObserveIdentityProviders(genericlisters configobserver.Listers, recorder ev
5054
return existingConfig, append(errs, err)
5155
}
5256

57+
buildTransport, err := buildIDPTransport(listers)
58+
if err != nil {
59+
return existingConfig, append(errs, err)
60+
}
61+
5362
// convert identity providers from config to oauth-configuration API and
5463
// extract the CMs and Secrets that need to be synchronized to the target NS
55-
convertedObservedIdentityProviders, observedSyncData, idpErrs := convertIdentityProviders(listers.ConfigMapLister, listers.SecretsLister, oauthConfig.Spec.IdentityProviders)
64+
convertedObservedIdentityProviders, observedSyncData, idpErrs := convertIdentityProviders(listers.SecretsLister, oauthConfig.Spec.IdentityProviders, buildTransport)
5665
if len(idpErrs) > 0 {
5766
return existingConfig, append(errs, idpErrs...)
5867
}
@@ -100,3 +109,22 @@ func GetIDPConfigSyncData(observedConfig map[string]interface{}) (*datasync.Conf
100109

101110
return datasync.NewConfigSyncDataFromJSON(currentSyncDataBytes)
102111
}
112+
113+
// buildIDPTransport returns a transport builder that uses the resolved proxy settings.
114+
func buildIDPTransport(listers configobservation.Listers) (transportForCABuilderFunc, error) {
115+
proxy, err := common.ResolveProxy(listers.FeatureGateAccessor, listers.OperatorAuthLister)
116+
if err != nil {
117+
return nil, err
118+
}
119+
120+
return func(caConfigMapName, caConfigMapKey string) (http.RoundTripper, error) {
121+
var caRefs []transport.CAReference
122+
if len(caConfigMapName) > 0 {
123+
caRefs = append(caRefs, transport.CAReference{ConfigMapName: caConfigMapName, ConfigMapKey: caConfigMapKey})
124+
}
125+
if len(proxy.TrustedCAName) > 0 {
126+
caRefs = append(caRefs, transport.CAReference{ConfigMapName: proxy.TrustedCAName, ConfigMapKey: "ca-bundle.crt"})
127+
}
128+
return transport.TransportForCARef(listers.ConfigMapLister, caRefs, proxy.HTTPProxy, proxy.HTTPSProxy, proxy.NoProxy)
129+
}, nil
130+
}

pkg/controllers/configobservation/oauth/observe_idps_test.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import (
1515
clocktesting "k8s.io/utils/clock/testing"
1616

1717
configv1 "github.com/openshift/api/config/v1"
18+
"github.com/openshift/api/features"
1819
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
20+
operatorv1listers "github.com/openshift/client-go/operator/listers/operator/v1"
21+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
1922
"github.com/openshift/library-go/pkg/operator/events"
2023
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
2124

@@ -195,11 +198,17 @@ func TestObserveIdentityProviders(t *testing.T) {
195198
}
196199

197200
syncerData := tt.previousSyncerData
201+
operatorAuthIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
198202
listers := configobservation.Listers{
199-
ConfigMapLister: corelistersv1.NewConfigMapLister(indexer),
200-
SecretsLister: corelistersv1.NewSecretLister(indexer),
201-
OAuthLister_: configlistersv1.NewOAuthLister(indexer),
202-
ResourceSync: &mockResourceSyncer{t: t, synced: syncerData},
203+
ConfigMapLister: corelistersv1.NewConfigMapLister(indexer),
204+
SecretsLister: corelistersv1.NewSecretLister(indexer),
205+
OAuthLister_: configlistersv1.NewOAuthLister(indexer),
206+
ResourceSync: &mockResourceSyncer{t: t, synced: syncerData},
207+
OperatorAuthLister: operatorv1listers.NewAuthenticationLister(operatorAuthIndexer),
208+
FeatureGateAccessor: featuregates.NewHardcodedFeatureGateAccess(
209+
nil,
210+
[]configv1.FeatureGateName{features.FeatureGateAuthenticationComponentProxy},
211+
),
203212
}
204213
eventsRecorder := events.NewInMemoryRecorder(t.Name(), clocktesting.NewFakePassiveClock(time.Now()))
205214

0 commit comments

Comments
 (0)