Skip to content

Commit c1080cc

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 convertIdentity- Providers signature changes from a ConfigMap lister to a transportForCABuilderFunc, decoupling transport construction from the IDP conversion logic. 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; service and endpoint checks remain proxy-free. Custom route controller: uses ComponentProxyFunc for the route availability health check, falling back to http.ProxyFromEnvironment. Operator startup: passes operatorAuthLister, featureGateAccessor, and operatorAuthInformer to all affected controllers.
1 parent 4b2d568 commit c1080cc

14 files changed

Lines changed: 579 additions & 64 deletions

File tree

pkg/controllers/configobservation/configobservercontroller/observe_config_controller.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ 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"
9+
operatorv1listers "github.com/openshift/client-go/operator/listers/operator/v1"
810
"github.com/openshift/library-go/pkg/controller/factory"
911
"github.com/openshift/library-go/pkg/operator/configobserver"
1012
"github.com/openshift/library-go/pkg/operator/configobserver/apiserver"
13+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
1114
configobserveroauth "github.com/openshift/library-go/pkg/operator/configobserver/oauth"
1215
"github.com/openshift/library-go/pkg/operator/events"
1316
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
@@ -27,6 +30,8 @@ func NewConfigObserver(
2730
resourceSyncer resourcesynccontroller.ResourceSyncer,
2831
enabledClusterCapabilities sets.String,
2932
eventRecorder events.Recorder,
33+
operatorAuthInformer operatorv1informers.AuthenticationInformer,
34+
featureGateAccessor featuregates.FeatureGateAccess,
3035
) factory.Controller {
3136
interestingNamespaces := []string{
3237
"openshift-authentication",
@@ -80,6 +85,16 @@ func NewConfigObserver(
8085
configobserver.WithPrefix(o, configobservation.OAuthServerConfigPrefix))
8186
}
8287

88+
// ObserveIdentityProviders reads component proxy settings from the operator Authentication CR
89+
// to build an HTTP transport for validating IDP endpoints. Register the informer so that
90+
// proxy config changes trigger a re-sync and IDP re-validation.
91+
var operatorAuthLister operatorv1listers.AuthenticationLister
92+
if operatorAuthInformer != nil {
93+
preRunCacheSynced = append(preRunCacheSynced, operatorAuthInformer.Informer().HasSynced)
94+
informers = append(informers, operatorAuthInformer.Informer())
95+
operatorAuthLister = operatorAuthInformer.Lister()
96+
}
97+
8398
listers := configobservation.Listers{
8499
ConfigMapLister: kubeInformersForNamespaces.ConfigMapLister(),
85100
SecretsLister: kubeInformersForNamespaces.SecretLister(),
@@ -91,6 +106,9 @@ func NewConfigObserver(
91106
OAuthLister_: configInformer.Config().V1().OAuths().Lister(),
92107
ResourceSync: resourceSyncer,
93108
PreRunCachesSynced: preRunCacheSynced,
109+
110+
OperatorAuthLister: operatorAuthLister,
111+
FeatureGateAccessor: featureGateAccessor,
94112
}
95113

96114
// 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: 12 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,10 @@ 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+
buildTransport := func(caConfigMapName, key string) (http.RoundTripper, error) {
215+
return transport.TransportForCARef(cmLister, caConfigMapName, key)
216+
}
217+
got, err := convertProviderConfigToIDPData(secretLister, tt.providerConfig, syncData, 0, buildTransport)
214218
if (err != nil) != tt.wantErr {
215219
t.Errorf("convertProviderConfigToIDPData() error = %v, wantErr %v", err, tt.wantErr)
216220
return
@@ -304,16 +308,19 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
304308
require.NoError(t, indexer.Add(secret))
305309
cmLister := corelistersv1.NewConfigMapLister(indexer)
306310
secretLister := corelistersv1.NewSecretLister(indexer)
311+
buildTransport := func(caConfigMapName, key string) (http.RoundTripper, error) {
312+
return transport.TransportForCARef(cmLister, caConfigMapName, key)
313+
}
307314

308315
t.Run("5xx responses are not cached", func(t *testing.T) {
309316
shouldError = true
310317
result, err := checkOIDCPasswordGrantFlow(
311-
cmLister,
312318
secretLister,
313319
server.URL+"/token",
314320
"test-client",
315321
configv1.ConfigMapNameReference{Name: ""},
316322
configv1.SecretNameReference{Name: "test-secret"},
323+
buildTransport,
317324
)
318325

319326
require.NoError(t, err)
@@ -325,12 +332,12 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
325332
responseContent = `{"error": "invalid_grant"}`
326333
shouldError = false
327334
result, err := checkOIDCPasswordGrantFlow(
328-
cmLister,
329335
secretLister,
330336
server.URL+"/token",
331337
"test-client",
332338
configv1.ConfigMapNameReference{Name: ""},
333339
configv1.SecretNameReference{Name: "test-secret"},
340+
buildTransport,
334341
)
335342

336343
require.NoError(t, err)
@@ -344,12 +351,12 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
344351

345352
shouldError = true
346353
res1, err := checkOIDCPasswordGrantFlow(
347-
cmLister,
348354
secretLister,
349355
server.URL+"/token",
350356
"test-client",
351357
configv1.ConfigMapNameReference{Name: ""},
352358
configv1.SecretNameReference{Name: "test-secret"},
359+
buildTransport,
353360
)
354361
require.NoError(t, err)
355362
require.False(t, res1)
@@ -359,12 +366,12 @@ func TestCheckOIDCPasswordGrantFlowCaching(t *testing.T) {
359366
responseContent = `{"error": "invalid_grant"}`
360367
shouldError = false
361368
res2, err := checkOIDCPasswordGrantFlow(
362-
cmLister,
363369
secretLister,
364370
server.URL+"/token",
365371
"test-client",
366372
configv1.ConfigMapNameReference{Name: ""},
367373
configv1.SecretNameReference{Name: "test-secret"},
374+
buildTransport,
368375
)
369376
require.NoError(t, err)
370377
require.True(t, res2)

pkg/controllers/configobservation/oauth/observe_idps.go

Lines changed: 28 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,32 @@ func ObserveIdentityProviders(genericlisters configobserver.Listers, recorder ev
5054
return existingConfig, append(errs, err)
5155
}
5256

57+
// build transport function, optionally with component-scoped proxy
58+
buildTransport := transportForCABuilderFunc(func(caConfigMapName, key string) (http.RoundTripper, error) {
59+
return transport.TransportForCARef(listers.ConfigMapLister, caConfigMapName, key)
60+
})
61+
authProxy, proxyErr := common.GetComponentProxyConfig(listers.FeatureGateAccessor, listers.OperatorAuthLister)
62+
if proxyErr != nil {
63+
klog.Warningf("failed to get component proxy config, falling back to cluster-wide proxy: %v", proxyErr)
64+
}
65+
if authProxy != nil {
66+
httpProxy, httpsProxy, noProxy := common.ResolveProxyConfig(authProxy, nil)
67+
var proxyCAData []byte
68+
if len(authProxy.TrustedCA.Name) > 0 {
69+
if caCM, caErr := listers.ConfigMapLister.ConfigMaps("openshift-config").Get(authProxy.TrustedCA.Name); caErr != nil {
70+
klog.Warningf("failed to load component proxy CA configmap %q: %v", authProxy.TrustedCA.Name, caErr)
71+
} else {
72+
proxyCAData = []byte(caCM.Data["ca-bundle.crt"])
73+
}
74+
}
75+
buildTransport = func(caConfigMapName, key string) (http.RoundTripper, error) {
76+
return transport.TransportForCARefWithProxy(listers.ConfigMapLister, caConfigMapName, key, httpProxy, httpsProxy, noProxy, proxyCAData)
77+
}
78+
}
79+
5380
// convert identity providers from config to oauth-configuration API and
5481
// 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)
82+
convertedObservedIdentityProviders, observedSyncData, idpErrs := convertIdentityProviders(listers.SecretsLister, oauthConfig.Spec.IdentityProviders, buildTransport)
5683
if len(idpErrs) > 0 {
5784
return existingConfig, append(errs, idpErrs...)
5885
}

pkg/controllers/customroute/custom_route_conditions.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/pem"
88
"fmt"
99
"net/http"
10+
"net/url"
1011
"reflect"
1112
"time"
1213

@@ -115,8 +116,8 @@ func degradeIfTimeElapsed(conditions []metav1.Condition, condition *v1.Condition
115116
}
116117
}
117118

118-
func checkRouteAvailablity(secretLister corev1listers.SecretLister, ingressConfig *configv1.Ingress, route *routev1.Route) []*v1.ConditionApplyConfiguration {
119-
if err := routeAvailablity(secretLister, route.Spec.Host, ingressConfig); err != nil {
119+
func checkRouteAvailablity(secretLister corev1listers.SecretLister, ingressConfig *configv1.Ingress, route *routev1.Route, proxyFn func(*http.Request) (*url.URL, error)) []*v1.ConditionApplyConfiguration {
120+
if err := routeAvailablity(secretLister, route.Spec.Host, ingressConfig, proxyFn); err != nil {
120121
now := metav1.Now()
121122
reason := "ErrorReachingOutToService"
122123
message := fmt.Sprintf("unexpected error at %s: %v", route.Spec.Host, err)
@@ -137,7 +138,7 @@ func checkRouteAvailablity(secretLister corev1listers.SecretLister, ingressConfi
137138
return nil
138139
}
139140

140-
func routeAvailablity(secretLister corev1listers.SecretLister, host string, ingress *configv1.Ingress) error {
141+
func routeAvailablity(secretLister corev1listers.SecretLister, host string, ingress *configv1.Ingress, proxyFn func(*http.Request) (*url.URL, error)) error {
141142
url := "https://" + host + "/healthz"
142143

143144
reqCtx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) // avoid waiting forever
@@ -161,7 +162,7 @@ func routeAvailablity(secretLister corev1listers.SecretLister, host string, ingr
161162
httpClient := http.Client{
162163
Timeout: 5 * time.Second,
163164
Transport: &http.Transport{
164-
Proxy: http.ProxyFromEnvironment,
165+
Proxy: proxyFn,
165166
TLSClientConfig: &tls.Config{
166167
RootCAs: rootCAs,
167168
},

0 commit comments

Comments
 (0)