Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,14 @@ spec:
- signers
verbs:
- approve
- apiGroups:
- config.openshift.io
resources:
- apiservers
verbs:
- get
- list
- watch
- apiGroups:
- config.openshift.io
resources:
Expand Down
62 changes: 60 additions & 2 deletions cmd/operator/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package main

import (
"context"
"crypto/tls"
"flag"
"fmt"
"os"
"strings"

configv1 "github.com/openshift/api/config/v1"
openshiftconfig "github.com/openshift/api/config/v1"
mapi "github.com/openshift/api/machine/v1beta1"
mcfg "github.com/openshift/api/machineconfiguration/v1"
libgocrypto "github.com/openshift/library-go/pkg/crypto"
tlspkg "github.com/openshift/controller-runtime-common/pkg/tls"
operators "github.com/operator-framework/api/pkg/operators/v2"
"github.com/operator-framework/operator-lib/leader"
monv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
Expand Down Expand Up @@ -46,6 +51,9 @@ import (
// ServiceAccount permissions used to watch operator on secrets.
//+kubebuilder:rbac:groups="",resources=secrets,verbs=watch

// RBAC for reading APIServer configuration to fetch TLS security profile
//+kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get;list;watch

var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
Expand Down Expand Up @@ -113,8 +121,9 @@ func main() {
os.Exit(1)
}

// get cluster configuration
ctx := ctrl.SetupSignalHandler()
ctx, cancel := context.WithCancel(ctrl.SetupSignalHandler())
defer cancel()

clusterConfig, err := cluster.NewConfig(ctx, cfg)
if err != nil {
setupLog.Error(err, "failed to get cluster configuration")
Expand All @@ -127,6 +136,30 @@ func main() {
os.Exit(1)
}

// Get TLS configuration from cluster config (fetched from APIServer during NewConfig)
tlsProfile := clusterConfig.TLSProfileSpec()
tlsAdherence := clusterConfig.TLSAdherencePolicy()

// Gate TLS profile enforcement on the adherence policy. WMCO was not previously honoring the
// cluster TLS profile, so per the centralized TLS config enhancement (StrictAllComponents mode)
// it should only enforce when ShouldHonorClusterTLSProfile returns true.
var metricsServerTLSOpts []func(*tls.Config)
if libgocrypto.ShouldHonorClusterTLSProfile(tlsAdherence) {
tlsConfigFn, unsupportedCiphers := tlspkg.NewTLSConfigFromProfile(tlsProfile)
if len(unsupportedCiphers) > 0 {
setupLog.Info("some cipher suites are not supported by Go and will be ignored",
"unsupportedCiphers", unsupportedCiphers)
}
metricsServerTLSOpts = []func(*tls.Config){
tlsConfigFn,
tlspkg.SetNextProtos(tlspkg.HTTP1NextProtos...),
}
setupLog.Info("TLS configuration loaded",
"minVersion", tlsProfile.MinTLSVersion,
"cipherSuites", len(tlsProfile.Ciphers)-len(unsupportedCiphers),
"adherencePolicy", tlsAdherence)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider bringing the final log entry down here to capture the final subset of cipher suites that will be used

setupLog.Info("TLS configuration loaded",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

setupLog.Info("platform", "type", clusterConfig.Platform())

if err := logconfig.ValidateLogConfig(); err != nil {
Expand Down Expand Up @@ -184,6 +217,7 @@ func main() {
BindAddress: metricsAddr,
SecureServing: true,
FilterProvider: filters.WithAuthenticationAndAuthorization,
TLSOpts: metricsServerTLSOpts,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curve preferences PR merged, consider setting CurvePreferences to include X25519MLKEM768 for post-quantum key exchange

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only the enhancement merged, implementation is pending https://github.com/openshift/controller-runtime-common/pull/22/changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add TLS strict obedience for PQC readiness

consider re-wording the PR description, the one thing that actually matters for post-quantum readiness is the hybrid KEM group X25519MLKEM768 and isn't applied in this change.

the MinVersion and CipherSuites don't get PQC on their own.

I'd rather see the description say plainly "lays the groundwork, curve preferences to follow once controller-runtime-common#22 lands" and get a Jira follow-up filed and linked now while the context's fresh

})
if err != nil {
Expand Down Expand Up @@ -307,6 +341,30 @@ func main() {
os.Exit(1)
}

// Setup TLS security profile watcher
// Watches for TLS profile or adherence policy changes and restarts pod to reload config
tlsWatcher := &tlspkg.SecurityProfileWatcher{
Client: mgr.GetClient(),
Comment thread
mansikulkarni96 marked this conversation as resolved.
InitialTLSProfileSpec: tlsProfile,
InitialTLSAdherencePolicy: tlsAdherence,
OnProfileChange: func(ctx context.Context, oldProfile, newProfile configv1.TLSProfileSpec) {
setupLog.Info("TLS security profile changed, initiating shutdown to reload configuration",
"oldMinVersion", oldProfile.MinTLSVersion,
"newMinVersion", newProfile.MinTLSVersion)
cancel()
},
OnAdherencePolicyChange: func(ctx context.Context, oldPolicy, newPolicy configv1.TLSAdherencePolicy) {
setupLog.Info("TLS adherence policy changed, initiating shutdown to reload configuration",
"oldPolicy", oldPolicy,
"newPolicy", newPolicy)
cancel()
},
}
if err := tlsWatcher.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to setup TLS security profile watcher")
os.Exit(1)
}

//+kubebuilder:scaffold:builder
// The above marker tells kubebuilder that this is where the SetupWithManager function should be inserted when new
// controllers are generated by Operator SDK.
Expand Down
9 changes: 9 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: windows-machine-config-operator
rules:
- apiGroups:
Expand Down Expand Up @@ -147,6 +148,14 @@ rules:
- signers
verbs:
- approve
- apiGroups:
- config.openshift.io
resources:
- apiservers
verbs:
- get
- list
- watch
- apiGroups:
- config.openshift.io
resources:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/go-logr/logr v1.4.4
github.com/openshift/api v0.0.0-20260729135743-72624b98ff3d
github.com/openshift/client-go v0.0.0-20260728123811-92b24dd0dd1f
github.com/openshift/controller-runtime-common v0.0.0-20260804161605-f9e4228f21e6
github.com/openshift/library-go v0.0.0-20260729082949-ed1b43415e01
github.com/operator-framework/api v0.41.0
github.com/operator-framework/operator-lib v0.4.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ github.com/openshift/api v0.0.0-20260729135743-72624b98ff3d h1:gCzFGzkzLaUX988DC
github.com/openshift/api v0.0.0-20260729135743-72624b98ff3d/go.mod h1:k6qH5QOVa5GDln2VVm8Jz4NV3Z7R2SATHFLwGS6Wh3M=
github.com/openshift/client-go v0.0.0-20260728123811-92b24dd0dd1f h1:dP7eA8s2WZWQNom5DDG7zHvl3FkcBVhtWUVXznr0Ezw=
github.com/openshift/client-go v0.0.0-20260728123811-92b24dd0dd1f/go.mod h1:pqFNk7AzeRdbhI1Ox7fFPhUTxcaeIXVLYjraSiig/ZM=
github.com/openshift/controller-runtime-common v0.0.0-20260804161605-f9e4228f21e6 h1:x6dgBCAlXd8yv40g87NFBgUZ+uBWOcGhDNIIzJTwLjs=
github.com/openshift/controller-runtime-common v0.0.0-20260804161605-f9e4228f21e6/go.mod h1:YVDrbC4muEYMejrIZkaQHhlH0bcBusL1UUBkPBxeVrI=
github.com/openshift/library-go v0.0.0-20260729082949-ed1b43415e01 h1:ZLF7koDByg791uXhb9WnQjQPmbwBqZrd0T/gpGM5m7g=
github.com/openshift/library-go v0.0.0-20260729082949-ed1b43415e01/go.mod h1:iahMN6YoNSRG1xyRj03YYU5iboJzv+UZFu9S0grJmQc=
github.com/operator-framework/api v0.5.2/go.mod h1:L7IvLd/ckxJEJg/t4oTTlnHKAJIP/p51AvEslW3wYdY=
Expand Down
1 change: 1 addition & 0 deletions hack/update_submodules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function update_libraries() {
go get github.com/openshift/api@$base_branch
go get github.com/openshift/client-go@$base_branch
go get github.com/openshift/library-go@$base_branch
go get github.com/openshift/controller-runtime-common@$base_branch
# react to main branch change in machine-config-operator
if [ "$base_branch" == "master" ]; then
go get github.com/openshift/machine-config-operator@main
Expand Down
38 changes: 34 additions & 4 deletions pkg/cluster/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
oconfig "github.com/openshift/api/config/v1"
configclient "github.com/openshift/client-go/config/clientset/versioned"
operatorv1 "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1"
tlspkg "github.com/openshift/controller-runtime-common/pkg/tls"
"golang.org/x/mod/semver"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -54,6 +55,10 @@ type Config interface {
Platform() oconfig.PlatformType
// Network returns network configuration for the OpenShift cluster
Network() Network
// TLSProfileSpec returns the TLS profile spec fetched from the APIServer
TLSProfileSpec() oconfig.TLSProfileSpec
// TLSAdherencePolicy returns the TLS adherence policy fetched from the APIServer
TLSAdherencePolicy() oconfig.TLSAdherencePolicy
}

// networkType holds information for a required network type
Expand All @@ -75,6 +80,10 @@ type config struct {
// platform indicates the cloud on which OpenShift cluster is running
// TODO: Remove this once we figure out how to be provider agnostic
platform oconfig.PlatformType
// tlsProfileSpec is the TLS profile spec fetched from the APIServer during startup
tlsProfileSpec oconfig.TLSProfileSpec
// tlsAdherencePolicy is the TLS adherence policy fetched from the APIServer during startup
tlsAdherencePolicy oconfig.TLSAdherencePolicy
}

func (c *config) Platform() oconfig.PlatformType {
Expand All @@ -85,6 +94,14 @@ func (c *config) Network() Network {
return c.network
}

func (c *config) TLSProfileSpec() oconfig.TLSProfileSpec {
return c.tlsProfileSpec
}

func (c *config) TLSAdherencePolicy() oconfig.TLSAdherencePolicy {
return c.tlsAdherencePolicy
}

// NewConfig returns a Config struct pertaining to the cluster configuration
func NewConfig(ctx context.Context, restConfig *rest.Config) (Config, error) {
// get OpenShift API config client.
Expand Down Expand Up @@ -117,11 +134,24 @@ func NewConfig(ctx context.Context, restConfig *rest.Config) (Config, error) {
if len(platformStatus.Type) == 0 {
return nil, fmt.Errorf("error getting platform type")
}

// Fetch TLS configuration from APIServer
apiServer, err := oclient.ConfigV1().APIServers().Get(ctx, "cluster", meta.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error getting APIServer configuration: %w", err)
}
tlsProfileSpec, err := tlspkg.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
if err != nil {
return nil, fmt.Errorf("error getting TLS profile from APIServer: %w", err)
}

return &config{
oclient: oclient,
operatorClient: operatorClient,
network: network,
platform: platformStatus.Type,
oclient: oclient,
operatorClient: operatorClient,
network: network,
platform: platformStatus.Type,
tlsProfileSpec: tlsProfileSpec,
tlsAdherencePolicy: apiServer.Spec.TLSAdherence,
Comment on lines +138 to +154

@coderabbitai coderabbitai Bot Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add table-driven tests for the APIServer TLS contract.

Test successful profile and policy storage. Test APIServer retrieval failure. Test invalid custom profile handling. This startup path now controls manager TLS behavior.

As per path instructions, “Add or update adjacent table-driven tests using testify/assert and require.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/cluster/config.go` around lines 138 - 154, Add adjacent table-driven
tests covering the config initialization path around APIServer retrieval and TLS
setup: assert successful storage of the resolved TLS profile and TLS adherence
policy, require an error when APIServers().Get fails, and require an error for
an invalid custom profile from tlspkg.GetTLSProfileSpec. Use testify/assert and
require consistently, and keep the tests focused on the APIServer TLS contract.

Source: Path instructions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1,

PTAL

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== Relevant Go files ==='
fd -t f -e go . pkg/cluster | sort

printf '%s\n' '=== Production file outline ==='
ast-grep outline pkg/cluster/config.go --items all --type function,interface,struct --match 'NewConfig|Config|config' || true

printf '%s\n' '=== Relevant test symbols and assertions ==='
rg -n -C 4 -g '*_test.go' \
  'NewConfig\s*\(|APIServers\(\)\.Get\s*\(|TLSProfileSpec\s*\(|TLSAdherencePolicy\s*\(|GetTLSProfileSpec\s*\(' \
  pkg/cluster || true

printf '%s\n' '=== Current PR diff for cluster config and tests ==='
git diff --no-ext-diff --unified=60 'origin/main...HEAD' -- pkg/cluster/config.go pkg/cluster/*_test.go 2>/dev/null || \
git diff --no-ext-diff --unified=60 -- pkg/cluster/config.go pkg/cluster/*_test.go

Length of output: 1814


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== Test file outline ==='
ast-grep outline pkg/cluster/config_test.go --items all --type function --view expanded

printf '%s\n' '=== Test file content ==='
cat -n pkg/cluster/config_test.go

printf '%s\n' '=== Repository state ==='
git status --short
printf '%s\n' '=== HEAD metadata ==='
git show -s --format='%H%n%s%n%D' HEAD
printf '%s\n' '=== Changed paths relative to HEAD parent ==='
git diff-tree --no-commit-id --name-status -r HEAD

Length of output: 12324


@jrvaldes, I reviewed the current test coverage.

The finding remains valid. pkg/cluster/config_test.go has no tests for the NewConfig APIServer TLS path.

Please add table-driven tests that cover:

  • successful storage of TLSProfileSpec() and TLSAdherencePolicy();
  • an error from APIServers().Get;
  • an invalid custom TLS profile returned to tlspkg.GetTLSProfileSpec.

This path configures the metrics server TLS behavior at operator startup.

You are interacting with an AI system.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated added tests.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, added tests

}, nil
}

Expand Down
83 changes: 83 additions & 0 deletions pkg/cluster/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
fakeconfigclient "github.com/openshift/client-go/config/clientset/versioned/fake"
fakeoperatorclient "github.com/openshift/client-go/operator/clientset/versioned/fake"
operatorclient "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1"
tlspkg "github.com/openshift/controller-runtime-common/pkg/tls"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -235,3 +236,85 @@ func TestGetDNS(t *testing.T) {
})
}
}

// TestTLSProfileFromAPIServer tests that TLS profile and adherence policy are correctly fetched from the APIServer
func TestTLSProfileFromAPIServer(t *testing.T) {
tests := []struct {
name string
tlsProfile *oconfig.TLSSecurityProfile
tlsAdherence oconfig.TLSAdherencePolicy
wantMinTLSVersion oconfig.TLSProtocolVersion
wantAdherence oconfig.TLSAdherencePolicy
wantErr bool
}{
{
name: "nil profile returns intermediate defaults",
tlsProfile: nil,
tlsAdherence: "",
wantMinTLSVersion: oconfig.TLSProfiles[oconfig.TLSProfileIntermediateType].MinTLSVersion,
wantAdherence: "",
},
{
name: "old profile type",
tlsProfile: &oconfig.TLSSecurityProfile{
Type: oconfig.TLSProfileOldType,
},
tlsAdherence: oconfig.TLSAdherencePolicyStrictAllComponents,
wantMinTLSVersion: oconfig.TLSProfiles[oconfig.TLSProfileOldType].MinTLSVersion,
wantAdherence: oconfig.TLSAdherencePolicyStrictAllComponents,
},
{
name: "modern profile type",
tlsProfile: &oconfig.TLSSecurityProfile{
Type: oconfig.TLSProfileModernType,
},
tlsAdherence: oconfig.TLSAdherencePolicyLegacyAdheringComponentsOnly,
wantMinTLSVersion: oconfig.TLSProfiles[oconfig.TLSProfileModernType].MinTLSVersion,
wantAdherence: oconfig.TLSAdherencePolicyLegacyAdheringComponentsOnly,
},
{
name: "custom profile with nil Custom field returns error",
tlsProfile: &oconfig.TLSSecurityProfile{
Type: oconfig.TLSProfileCustomType,
},
wantErr: true,
},
{
name: "custom profile with valid spec",
tlsProfile: &oconfig.TLSSecurityProfile{
Type: oconfig.TLSProfileCustomType,
Custom: &oconfig.CustomTLSProfile{
TLSProfileSpec: oconfig.TLSProfileSpec{
MinTLSVersion: oconfig.VersionTLS13,
},
},
},
wantMinTLSVersion: oconfig.VersionTLS13,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
apiServer := &oconfig.APIServer{
ObjectMeta: meta.ObjectMeta{Name: "cluster"},
Spec: oconfig.APIServerSpec{
TLSSecurityProfile: tt.tlsProfile,
TLSAdherence: tt.tlsAdherence,
},
}
fakeClient := fakeconfigclient.NewSimpleClientset(apiServer)

got, err := fakeClient.ConfigV1().APIServers().Get(context.Background(), "cluster", meta.GetOptions{})
require.NoError(t, err)

profileSpec, err := tlspkg.GetTLSProfileSpec(got.Spec.TLSSecurityProfile)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantMinTLSVersion, profileSpec.MinTLSVersion)
assert.Equal(t, tt.wantAdherence, got.Spec.TLSAdherence)
})
}
}
Loading