Skip to content

Commit

Permalink
operator: start externaloidc controller behind a featuregates accessor
Browse files Browse the repository at this point in the history
  • Loading branch information
liouk committed Nov 21, 2024
1 parent 5d8965d commit 57c8bbc
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 18 deletions.
38 changes: 20 additions & 18 deletions pkg/controllers/externaloidc/externaloidc_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
Expand All @@ -18,12 +19,11 @@ import (
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resource/retry"
"github.com/openshift/library-go/pkg/operator/v1helpers"
"golang.org/x/net/http/httpproxy"

"k8s.io/apimachinery/pkg/api/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/sets"
apiserverv1beta1 "k8s.io/apiserver/pkg/apis/apiserver/v1beta1"
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
Expand All @@ -33,12 +33,6 @@ import (
"k8s.io/utils/ptr"
)

var (
cfgScheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(cfgScheme, serializer.EnableStrict)
serializerInfo, _ = runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
)

const (
configNamespace = "openshift-config"
managedNamespace = "openshift-config-managed"
Expand All @@ -47,12 +41,6 @@ const (
oidcDiscoveryEndpointPath = "/.well-known/openid-configuration"
)

func init() {
if err := apiserverv1beta1.AddToScheme(cfgScheme); err != nil {
panic(err)
}
}

type externalOIDCController struct {
name string
eventName string
Expand Down Expand Up @@ -121,11 +109,11 @@ func (c *externalOIDCController) sync(ctx context.Context, syncCtx factory.SyncC
return err
}

encoded, err := runtime.Encode(codecs.EncoderForVersion(serializerInfo.Serializer, apiserverv1beta1.ConfigSchemeGroupVersion), authConfig)
b, err := json.Marshal(authConfig)
if err != nil {
return fmt.Errorf("could not marshal auth config into JSON: %v", err)
}
authConfigJSON := strings.TrimSpace(string(encoded))
authConfigJSON := string(b)

existingCM, err := c.configMapLister.ConfigMaps(managedNamespace).Get(targetAuthConfigCMName)
if err != nil && !apierrors.IsNotFound(err) {
Expand All @@ -142,7 +130,7 @@ func (c *externalOIDCController) sync(ctx context.Context, syncCtx factory.SyncC
}

cm := corev1ac.ConfigMap(targetAuthConfigCMName, managedNamespace).WithData(map[string]string{authConfigDataKey: authConfigJSON})
if _, err := c.configMaps.ConfigMaps(managedNamespace).Apply(ctx, cm, metav1.ApplyOptions{FieldManager: c.name}); err != nil {
if _, err := c.configMaps.ConfigMaps(managedNamespace).Apply(ctx, cm, metav1.ApplyOptions{FieldManager: c.name, Force: true}); err != nil {
return fmt.Errorf("could not apply changes to auth configmap %s/%s: %v", managedNamespace, targetAuthConfigCMName, err)
}

Expand All @@ -154,7 +142,13 @@ func (c *externalOIDCController) sync(ctx context.Context, syncCtx factory.SyncC
// generateAuthConfig creates a structured JWT AuthenticationConfiguration for OIDC
// from the configuration found in the authentication/cluster resource
func (c *externalOIDCController) generateAuthConfig(auth configv1.Authentication) (*apiserverv1beta1.AuthenticationConfiguration, error) {
authConfig := apiserverv1beta1.AuthenticationConfiguration{}
authConfig := apiserverv1beta1.AuthenticationConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "AuthenticationConfiguration",
APIVersion: "apiserver.config.k8s.io/v1beta1",
},
}

for _, provider := range auth.Spec.OIDCProviders {
jwt := apiserverv1beta1.JWTAuthenticator{
Issuer: apiserverv1beta1.Issuer{
Expand Down Expand Up @@ -204,6 +198,8 @@ func (c *externalOIDCController) generateAuthConfig(auth configv1.Authentication
} else {
jwt.ClaimMappings.Username.Prefix = &provider.ClaimMappings.Username.Prefix.PrefixString
}
default:
return nil, fmt.Errorf("invalid username prefix policy: %s", provider.ClaimMappings.Username.PrefixPolicy)
}

for i, rule := range provider.ClaimValidationRules {
Expand Down Expand Up @@ -334,6 +330,12 @@ func validateCACert(hostURL url.URL, caCertPool *x509.CertPool) error {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: caCertPool},
Proxy: func(*http.Request) (*url.URL, error) {
if proxyConfig := httpproxy.FromEnvironment(); len(proxyConfig.HTTPSProxy) > 0 {
return url.Parse(proxyConfig.HTTPSProxy)
}
return nil, nil
},
},
Timeout: 5 * time.Second,
}
Expand Down
21 changes: 21 additions & 0 deletions pkg/controllers/externaloidc/externaloidc_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ var (
})

baseAuthConfig = apiserverv1beta1.AuthenticationConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "AuthenticationConfiguration",
APIVersion: "apiserver.config.k8s.io/v1beta1",
},
JWT: []apiserverv1beta1.JWTAuthenticator{
{
Issuer: apiserverv1beta1.Issuer{
Expand Down Expand Up @@ -429,6 +433,23 @@ func TestExternalOIDCController_generateAuthConfig(t *testing.T) {
}),
expectError: true,
},
{
name: "auth config invalid prefix policy",
caBundleConfigMap: &baseCABundleConfigMap,
auth: *authWithUpdates(baseAuthResource, []func(auth *configv1.Authentication){
func(auth *configv1.Authentication) {
for i := range auth.Spec.OIDCProviders {
auth.Spec.OIDCProviders[i].ClaimMappings.Username = configv1.UsernameClaimMapping{
TokenClaimMapping: configv1.TokenClaimMapping{
Claim: "username",
},
PrefixPolicy: configv1.UsernamePrefixPolicy("invalid-policy"),
}
}
},
}),
expectError: true,
},
{
name: "auth config with nil claim in validation rule",
caBundleConfigMap: &baseCABundleConfigMap,
Expand Down
7 changes: 7 additions & 0 deletions pkg/operator/replacement_starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,5 +318,12 @@ func CreateOperatorStarter(ctx context.Context, authOperatorInput *authenticatio
ret.ControllerRunFns = append(ret.ControllerRunFns, oauthAPIServerRunFns...)
ret.ControllerNamedRunOnceFns = append(ret.ControllerNamedRunOnceFns, oauthAPIServerRunOnceFns...)

externalOIDCRunOnceFns, externalOIDCRunFns, err := prepareExternalOIDC(ctx, authOperatorInput, informerFactories)
if err != nil {
return nil, fmt.Errorf("unable to prepare external OIDC: %w", err)
}
ret.ControllerRunFns = append(ret.ControllerRunFns, externalOIDCRunFns...)
ret.ControllerNamedRunOnceFns = append(ret.ControllerNamedRunOnceFns, externalOIDCRunOnceFns...)

return ret, nil
}
49 changes: 49 additions & 0 deletions pkg/operator/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import (
"github.com/openshift/multi-operator-manager/pkg/library/libraryapplyconfiguration"

configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/api/features"
operatorv1 "github.com/openshift/api/operator/v1"
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/configobservation/configobservercontroller"
componentroutesecretsync "github.com/openshift/cluster-authentication-operator/pkg/controllers/customroute"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/deployment"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/ingressnodesavailable"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/ingressstate"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/metadata"
Expand All @@ -39,6 +41,7 @@ import (
workloadcontroller "github.com/openshift/library-go/pkg/operator/apiserver/controller/workload"
apiservercontrollerset "github.com/openshift/library-go/pkg/operator/apiserver/controllerset"
"github.com/openshift/library-go/pkg/operator/certrotation"
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
"github.com/openshift/library-go/pkg/operator/csr"
"github.com/openshift/library-go/pkg/operator/encryption"
"github.com/openshift/library-go/pkg/operator/encryption/controllers/migrators"
Expand Down Expand Up @@ -671,6 +674,52 @@ func prepareOauthAPIServerOperator(
return runOnceFns, runFns, nil
}

func prepareExternalOIDC(
ctx context.Context,
authOperatorInput *authenticationOperatorInput,
informerFactories authenticationOperatorInformerFactories,
) ([]libraryapplyconfiguration.NamedRunOnce, []libraryapplyconfiguration.RunFunc, error) {

// By default, this will exit(0) if the featuregates change
featureGateAccessor := featuregates.NewFeatureGateAccess(
status.VersionForOperatorFromEnv(), "0.0.1-snapshot",
informerFactories.operatorConfigInformer.Config().V1().ClusterVersions(),
informerFactories.operatorConfigInformer.Config().V1().FeatureGates(),
authOperatorInput.eventRecorder,
)
go featureGateAccessor.Run(ctx)
go informerFactories.operatorConfigInformer.Start(ctx.Done())

var featureGates featuregates.FeatureGate
select {
case <-featureGateAccessor.InitialFeatureGatesObserved():
featureGates, _ = featureGateAccessor.CurrentFeatureGates()
case <-time.After(1 * time.Minute):
return nil, nil, fmt.Errorf("timed out waiting for FeatureGate detection")
}

if !featureGates.Enabled(features.FeatureGateExternalOIDC) {
return nil, nil, nil
}

externalOIDCController := externaloidc.NewExternalOIDCController(
informerFactories.kubeInformersForNamespaces,
informerFactories.operatorConfigInformer,
authOperatorInput.authenticationOperatorClient,
authOperatorInput.kubeClient.CoreV1(),
authOperatorInput.eventRecorder,
)

runOnceFns := []libraryapplyconfiguration.NamedRunOnce{
libraryapplyconfiguration.AdaptSyncFn(authOperatorInput.eventRecorder, "TODO-other-externalOIDCController", externalOIDCController.Sync),
}
runFns := []libraryapplyconfiguration.RunFunc{
libraryapplyconfiguration.AdaptRunFn(externalOIDCController.Run),
}

return runOnceFns, runFns, nil
}

func singleNameListOptions(name string) func(opts *metav1.ListOptions) {
return func(opts *metav1.ListOptions) {
opts.FieldSelector = fields.OneTermEqualSelector("metadata.name", name).String()
Expand Down

0 comments on commit 57c8bbc

Please sign in to comment.