Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 97 additions & 41 deletions pkg/cmd/operator/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/utils/clock"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -84,8 +85,10 @@ const (
)

type ControllerManagerOptions struct {
LogLevel string
Kubeconfig string
LogLevel string
Kubeconfig string
TLSMinVersion string
TLSCipherSuites []string
}

func NewOperator() *cobra.Command {
Expand Down Expand Up @@ -260,31 +263,13 @@ func NewOperator() *cobra.Command {
return err
}

// Retrieve the initial TLS Adherence policy for the TLS profile watcher
initialTLSAdherence, err := utiltls.FetchAPIServerTLSAdherencePolicy(ctx, k8sClient)
tlsConfig, initialTLSProfile, initialTLSAdherence, tlsOverrideFromFlags, err := resolveTLSConfig(
ctx, k8sClient, opts.TLSMinVersion, opts.TLSCipherSuites)
if err != nil {
log.WithError(err).Error("failed to fetch initial TLS adherence")
log.WithError(err).Error("failed to resolve TLS configuration")
return err
}

// Retrieve the initial TLS profile to use for the TLS profile watcher
initialTLSProfile, err := utiltls.FetchAPIServerTLSProfile(ctx, k8sClient)
if err != nil {
log.WithError(err).Error("failed to fetch initial TLS profile")
return err
}

tlsConfig := func(*tls.Config) {}

if libgocrypto.ShouldHonorClusterTLSProfile(initialTLSAdherence) {
var unsupportedCiphers []string
// Create the TLS configuration function for the server endpoints.
tlsConfig, unsupportedCiphers = utiltls.NewTLSConfigFromProfile(initialTLSProfile)
if len(unsupportedCiphers) > 0 {
log.Infof("TLS configuration contains unsupported ciphers that will be ignored: %v", unsupportedCiphers)
}
}

// Create a new Cmd to provide shared dependencies and start components
log.Info("setting up managers")
mgr, err := manager.New(cfg, manager.Options{
Expand Down Expand Up @@ -327,28 +312,32 @@ func NewOperator() *cobra.Command {

// Setup all Controllers
log.Info("setting up controllers")
if err := controller.AddToManager(mgr, rootMgr, opts.Kubeconfig, coreClient); err != nil {
if err := controller.AddToManager(ctx, mgr, rootMgr, opts.Kubeconfig, coreClient, opts.TLSMinVersion, opts.TLSCipherSuites, tlsOverrideFromFlags); err != nil {
log.WithError(err).Error("unable to register controllers to the manager")
return err
}

// Set up the TLS security profile watcher controller.
// This will trigger a graceful shutdown when the TLS profile changes.
if err := (&utiltls.SecurityProfileWatcher{
Client: mgr.GetClient(),
InitialTLSProfileSpec: initialTLSProfile,
InitialTLSAdherencePolicy: initialTLSAdherence,
OnProfileChange: func(ctx context.Context, oldTLSProfileSpec, newTLSProfileSpec configv1.TLSProfileSpec) {
log.Infof("TLS profile changed from %v to %v, restarting operator", oldTLSProfileSpec, newTLSProfileSpec)
cancel()
},
OnAdherencePolicyChange: func(ctx context.Context, oldTLSAdherencePolicy configv1.TLSAdherencePolicy, newTLSAdherencePolicy configv1.TLSAdherencePolicy) {
log.Infof("TLS adherence policy changed from %v to %v, restarting operator", oldTLSAdherencePolicy, newTLSAdherencePolicy)
cancel()
},
}).SetupWithManager(mgr); err != nil {
log.WithError(err).Error("failed to set up TLS profile watcher")
return err
// When TLS is overridden via CLI flags, the watcher is not needed since
// we're not reading from apiservers.config.openshift.io/cluster.
if tlsOverrideFromFlags {
log.Info("TLS security profile watcher disabled because TLS is configured via CLI flags")
} else {
if err := (&utiltls.SecurityProfileWatcher{
Client: mgr.GetClient(),
InitialTLSProfileSpec: initialTLSProfile,
InitialTLSAdherencePolicy: initialTLSAdherence,
OnProfileChange: func(ctx context.Context, oldTLSProfileSpec, newTLSProfileSpec configv1.TLSProfileSpec) {
log.Infof("TLS profile changed from %v to %v, restarting operator", oldTLSProfileSpec, newTLSProfileSpec)
cancel()
},
OnAdherencePolicyChange: func(ctx context.Context, oldTLSAdherencePolicy configv1.TLSAdherencePolicy, newTLSAdherencePolicy configv1.TLSAdherencePolicy) {
log.Infof("TLS adherence policy changed from %v to %v, restarting operator", oldTLSAdherencePolicy, newTLSAdherencePolicy)
cancel()
},
}).SetupWithManager(mgr); err != nil {
log.WithError(err).Error("failed to set up TLS profile watcher")
return err
}
}

// Start the managers
Expand Down Expand Up @@ -456,6 +445,8 @@ func NewOperator() *cobra.Command {

cmd.PersistentFlags().StringVar(&opts.LogLevel, "log-level", defaultLogLevel, "Log level (debug,info,warn,error,fatal)")
cmd.PersistentFlags().StringVar(&opts.Kubeconfig, "kubeconfig", "", "Path to the kubeconfig to use.")
cmd.PersistentFlags().StringVar(&opts.TLSMinVersion, "tls-min-version", "", "Minimum TLS version supported. When set, overrides the cluster-wide TLS profile from APIServer CR. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13")
cmd.PersistentFlags().StringSliceVar(&opts.TLSCipherSuites, "tls-cipher-suites", nil, "Comma-separated list of cipher suites for the server. When set, overrides the cluster-wide TLS profile from APIServer CR. Values are Go crypto/tls cipher suite names.")
cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)
initializeGlog(cmd.PersistentFlags())
flag.CommandLine.Parse([]string{})
Expand Down Expand Up @@ -508,6 +499,71 @@ func (writer glogWriter) Write(data []byte) (n int, err error) {
return len(data), nil
}

func resolveTLSConfig(ctx context.Context, k8sClient client.Client, tlsMinVersion string, tlsCipherSuites []string) (
func(*tls.Config), configv1.TLSProfileSpec, configv1.TLSAdherencePolicy, bool, error,
) {
if tlsMinVersion != "" || len(tlsCipherSuites) > 0 {
log.Info("TLS configuration overridden via CLI flags")

var minVersion uint16
if tlsMinVersion != "" {
var err error
minVersion, err = cliflag.TLSVersion(tlsMinVersion)
if err != nil {
return nil, configv1.TLSProfileSpec{}, "", false,
fmt.Errorf("invalid --tls-min-version value: %w", err)
}
} else {
minVersion = cliflag.DefaultTLSVersion()
}

var cipherSuites []uint16
if len(tlsCipherSuites) > 0 {
var err error
cipherSuites, err = cliflag.TLSCipherSuites(tlsCipherSuites)
if err != nil {
return nil, configv1.TLSProfileSpec{}, "", false,
fmt.Errorf("invalid --tls-cipher-suites value: %w", err)
}
}

tlsConfigFunc := func(cfg *tls.Config) {
cfg.MinVersion = minVersion
if minVersion < tls.VersionTLS13 {
cfg.CipherSuites = cipherSuites
} else if len(tlsCipherSuites) > 0 {
log.Warning("TLS 1.3 cipher suites are not configurable in Go, ignoring --tls-cipher-suites")
}
}

return tlsConfigFunc, configv1.TLSProfileSpec{}, "", true, nil
}

initialTLSAdherence, err := utiltls.FetchAPIServerTLSAdherencePolicy(ctx, k8sClient)
if err != nil {
return nil, configv1.TLSProfileSpec{}, "", false,
fmt.Errorf("failed to fetch TLS adherence policy: %w", err)
}

initialTLSProfile, err := utiltls.FetchAPIServerTLSProfile(ctx, k8sClient)
if err != nil {
return nil, configv1.TLSProfileSpec{}, "", false,
fmt.Errorf("failed to fetch TLS profile: %w", err)
}

tlsConfigFunc := func(*tls.Config) {}

if libgocrypto.ShouldHonorClusterTLSProfile(initialTLSAdherence) {
var unsupportedCiphers []string
tlsConfigFunc, unsupportedCiphers = utiltls.NewTLSConfigFromProfile(initialTLSProfile)
if len(unsupportedCiphers) > 0 {
log.Infof("TLS configuration contains unsupported ciphers that will be ignored: %v", unsupportedCiphers)
}
}

return tlsConfigFunc, initialTLSProfile, initialTLSAdherence, false, nil
}

func terminateWhenProxyChanges(path string, cancel context.CancelFunc, done <-chan struct{}) {
// read the contents of the configmap
fileContents := map[string][]byte{}
Expand Down
14 changes: 11 additions & 3 deletions pkg/operator/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package controller

import (
"context"

configv1 "github.com/openshift/api/config/v1"
awsactuator "github.com/openshift/cloud-credential-operator/pkg/aws/actuator"
"github.com/openshift/cloud-credential-operator/pkg/azure"
Expand Down Expand Up @@ -51,7 +53,6 @@ const (
func init() {
AddToManagerFuncs = append(AddToManagerFuncs, metrics.Add)
AddToManagerFuncs = append(AddToManagerFuncs, secretannotator.Add)
AddToManagerFuncs = append(AddToManagerFuncs, podidentity.Add)
AddToManagerFuncs = append(AddToManagerFuncs, status.Add)
AddToManagerFuncs = append(AddToManagerFuncs, loglevel.Add)
AddToManagerFuncs = append(AddToManagerFuncs, cleanup.Add)
Expand All @@ -65,13 +66,20 @@ var AddToManagerFuncs []func(manager.Manager, manager.Manager, string) error
// AddToManagerWithActuatorFuncs is a list of functions to add all Controllers with Actuators to the Manager
var AddToManagerWithActuatorFuncs []func(manager.Manager, manager.Manager, actuator.Actuator, configv1.PlatformType, corev1client.CoreV1Interface) error

// AddToManager adds all Controllers to the Manager
func AddToManager(m, rootM manager.Manager, explicitKubeconfig string, coreClient corev1client.CoreV1Interface) error {
// AddToManager adds all Controllers to the Manager with optional TLS CLI flag overrides.
// TODO: consolidate tlsMinVersion, tlsCipherSuites, tlsOverrideFromFlags into a struct
// if more TLS parameters are added — the bool is derivable from the flag values.
func AddToManager(ctx context.Context, m, rootM manager.Manager, explicitKubeconfig string, coreClient corev1client.CoreV1Interface, tlsMinVersion string, tlsCipherSuites []string, tlsOverrideFromFlags bool) error {
for _, f := range AddToManagerFuncs {
if err := f(m, rootM, explicitKubeconfig); err != nil {
return err
}
}

if err := podidentity.AddWithTLS(ctx, m, rootM, explicitKubeconfig, tlsMinVersion, tlsCipherSuites, tlsOverrideFromFlags); err != nil {
return err
}

for _, f := range AddToManagerWithActuatorFuncs {
// Check for supported platform types, dummy if not found:
// TODO: Use infrastructure type to determine this in future, it's not being populated yet:
Expand Down
66 changes: 51 additions & 15 deletions pkg/operator/podidentity/podidentitywebhook_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,15 @@ func (c *podIdentityController) Start(ctx context.Context) error {
return nil
}

func AddWithTLS(ctx context.Context, mgr, _ manager.Manager, kubeconfig string, tlsMinVersion string, tlsCipherSuites []string, tlsOverrideFromFlags bool) error {
return addInternal(ctx, mgr, kubeconfig, tlsMinVersion, tlsCipherSuites, tlsOverrideFromFlags)
}

func Add(mgr, _ manager.Manager, kubeconfig string) error {
return addInternal(context.TODO(), mgr, kubeconfig, "", nil, false)
}

func addInternal(ctx context.Context, mgr manager.Manager, kubeconfig string, tlsMinVersion string, tlsCipherSuites []string, tlsOverrideFromFlags bool) error {
infraStatus, err := platform.GetInfraStatusUsingKubeconfig(kubeconfig)
if err != nil {
return err
Expand All @@ -172,7 +180,6 @@ func Add(mgr, _ manager.Manager, kubeconfig string) error {
log.WithField("controller", controllerName).Warn("Failed to get platform type")
return nil
}
ctx := context.TODO()
logger := log.WithFields(log.Fields{"platform": platformType, "controller": controllerName})

config := mgr.GetConfig()
Expand Down Expand Up @@ -224,18 +231,27 @@ func Add(mgr, _ manager.Manager, kubeconfig string) error {
return err
}

tlsAdherence, err := utiltls.FetchAPIServerTLSAdherencePolicy(ctx, k8sClient)
if err != nil {
return err
}
if tlsOverrideFromFlags {
logger.Info("Webhook TLS configuration overridden via CLI flags")
r.tlsProfileSpec = configv1.TLSProfileSpec{
MinTLSVersion: configv1.TLSProtocolVersion(tlsMinVersion),
Ciphers: tlsCipherSuites,
}
r.tlsFromFlags = true
} else {
tlsAdherence, err := utiltls.FetchAPIServerTLSAdherencePolicy(ctx, k8sClient)
if err != nil {
return err
}

initialTLSProfile, err := utiltls.FetchAPIServerTLSProfile(ctx, k8sClient)
if err != nil {
return err
}
initialTLSProfile, err := utiltls.FetchAPIServerTLSProfile(ctx, k8sClient)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if err != nil {
return err
}

if libgocrypto.ShouldHonorClusterTLSProfile(tlsAdherence) {
r.tlsProfileSpec = initialTLSProfile
if libgocrypto.ShouldHonorClusterTLSProfile(tlsAdherence) {
r.tlsProfileSpec = initialTLSProfile
}
}

c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
Expand Down Expand Up @@ -321,6 +337,7 @@ type staticResourceReconciler struct {
cache resourceapply.ResourceCache
podIdentityType PodIdentityManifestSource
tlsProfileSpec configv1.TLSProfileSpec
tlsFromFlags bool
// degradedSince tracks when reconciliation errors started. On pod restart
// this resets to zero; seedDegradedSince recovers it from the published
// ClusterOperator Degraded condition so the grace period is not re-granted.
Expand Down Expand Up @@ -501,10 +518,29 @@ func (r *staticResourceReconciler) ReconcileResources(ctx context.Context) (*app

requestedDeployment.Spec.Template.Spec.Containers[0].Image = r.imagePullSpec

err = r.podIdentityType.ApplyDeploymentSubstitutionsInPlace(requestedDeployment, r.client, r.logger, r.tlsProfileSpec)
if err != nil {
r.logger.WithError(err).Error("error substituting Deployment")
return nil, err
if r.tlsFromFlags {
// CLI flags provide IANA cipher names, but ApplyDeploymentSubstitutionsInPlace
// runs OpenSSL-to-IANA conversion that would silently drop them. Pass an empty
// TLSProfileSpec so non-TLS substitutions still run, then inject TLS args directly.
err = r.podIdentityType.ApplyDeploymentSubstitutionsInPlace(requestedDeployment, r.client, r.logger, configv1.TLSProfileSpec{})
if err != nil {
r.logger.WithError(err).Error("error substituting Deployment")
return nil, err
}
if r.tlsProfileSpec.MinTLSVersion != "" {
requestedDeployment.Spec.Template.Spec.Containers[0].Command = append(requestedDeployment.Spec.Template.Spec.Containers[0].Command,
fmt.Sprintf("--tls-min-version=%s", r.tlsProfileSpec.MinTLSVersion))
}
if len(r.tlsProfileSpec.Ciphers) > 0 && r.tlsProfileSpec.MinTLSVersion != configv1.TLSProtocolVersion("VersionTLS13") {
requestedDeployment.Spec.Template.Spec.Containers[0].Command = append(requestedDeployment.Spec.Template.Spec.Containers[0].Command,
fmt.Sprintf("--tls-cipher-suites=%s", strings.Join(r.tlsProfileSpec.Ciphers, ",")))
}
} else {
err = r.podIdentityType.ApplyDeploymentSubstitutionsInPlace(requestedDeployment, r.client, r.logger, r.tlsProfileSpec)
if err != nil {
r.logger.WithError(err).Error("error substituting Deployment")
return nil, err
}
}

resultDeployment, modified, err := resourceapply.ApplyDeployment(ctx, r.clientset.AppsV1(), r.eventRecorder, requestedDeployment, r.deploymentGeneration)
Expand Down