Skip to content

Commit 0bb07b2

Browse files
committed
feat: override TLS Config via flags
1 parent 519b823 commit 0bb07b2

3 files changed

Lines changed: 168 additions & 57 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ require (
113113
gopkg.in/yaml.v2 v2.4.0 // indirect
114114
gopkg.in/yaml.v3 v3.0.1 // indirect
115115
k8s.io/apiextensions-apiserver v0.35.2 // indirect
116-
k8s.io/component-base v0.35.2 // indirect; indirectpackag
116+
k8s.io/component-base v0.35.2
117117
k8s.io/kube-aggregator v0.35.2 // indirect
118118
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
119119
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect

main.go

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,14 @@ import (
3030
"github.com/openshift/cluster-machine-approver/pkg/controller"
3131
"github.com/openshift/cluster-machine-approver/pkg/metrics"
3232
utiltls "github.com/openshift/controller-runtime-common/pkg/tls"
33-
libgocrypto "github.com/openshift/library-go/pkg/crypto"
3433
flag "github.com/spf13/pflag"
3534
certificatesv1 "k8s.io/api/certificates/v1"
3635
corev1 "k8s.io/api/core/v1"
37-
"k8s.io/apimachinery/pkg/runtime"
3836
"k8s.io/apimachinery/pkg/runtime/schema"
3937
"k8s.io/client-go/rest"
4038
"k8s.io/client-go/tools/clientcmd"
4139
"k8s.io/client-go/tools/leaderelection/resourcelock"
40+
cliflag "k8s.io/component-base/cli/flag"
4241
"k8s.io/klog/v2"
4342
"k8s.io/utils/clock"
4443
control "sigs.k8s.io/controller-runtime"
@@ -71,17 +70,14 @@ func main() {
7170
var leaderElectResourceName string
7271
var leaderElectResourceNamespace string
7372
var metricsBindAddress string
73+
var tlsMinVersionFlag string
74+
var tlsCipherSuitesFlag []string
7475

7576
flagSet := flag.NewFlagSet("cluster-machine-approver", flag.ExitOnError)
7677

7778
// Set logger for controller-runtime
7879
control.SetLogger(klog.NewKlogr())
7980

80-
scheme := runtime.NewScheme()
81-
if err := configv1.AddToScheme(scheme); err != nil {
82-
klog.Fatalf("unable to add configv1 to scheme: %v", err)
83-
}
84-
8581
klog.InitFlags(nil)
8682
flagSet.AddGoFlagSet(goflag.CommandLine)
8783

@@ -100,6 +96,8 @@ func main() {
10096
flagSet.StringVar(&leaderElectResourceName, "leader-elect-resource-name", "cluster-machine-approver-leader", "the name of the resource that leader election will use for holding the leader lock.")
10197
flagSet.StringVar(&leaderElectResourceNamespace, "leader-elect-resource-namespace", "openshift-cluster-machine-approver", "the namespace in which the leader election resource will be created.")
10298
flagSet.StringVar(&metricsBindAddress, "metrics-bind-address", metrics.DefaultMetricsBindAddress, "the address the metrics endpoint binds to.")
99+
flagSet.StringVar(&tlsMinVersionFlag, "tls-min-version", "", "Minimum TLS version supported. When set with --tls-cipher-suites, overrides the cluster-wide TLS profile. Possible values: "+strings.Join(cliflag.TLSPossibleVersions(), ", "))
100+
flagSet.StringSliceVar(&tlsCipherSuitesFlag, "tls-cipher-suites", nil, "Comma-separated list of cipher suites for the server. When set with --tls-min-version, overrides the cluster-wide TLS profile. Possible values: "+strings.Join(cliflag.TLSCipherPossibleValues(), ", "))
103101

104102
// Deprecated options
105103
flagSet.StringVar(&apiGroup, "apigroup", "", "API group for machines")
@@ -111,6 +109,11 @@ func main() {
111109
klog.Fatal("Cannot set both --apigroup and --api-group-version options together.")
112110
}
113111

112+
tlsOverrideFromFlags := tlsMinVersionFlag != "" || len(tlsCipherSuitesFlag) > 0
113+
if tlsOverrideFromFlags && (tlsMinVersionFlag == "" || len(tlsCipherSuitesFlag) == 0) {
114+
klog.Fatal("Both --tls-min-version and --tls-cipher-suites must be provided when either is set.")
115+
}
116+
114117
var parsedAPIGroupVersions []schema.GroupVersion
115118

116119
if len(apiGroupVersions) > 0 {
@@ -151,35 +154,10 @@ func main() {
151154
klog.Fatalf("Can't set client configs: %v", err)
152155
}
153156

154-
k8sClient, err := client.New(workloadConfig, client.Options{Scheme: scheme})
155-
if err != nil {
156-
klog.Fatalf("unable to create Kubernetes client: %v", err)
157-
}
158-
159-
// Fetch the TLS adherence policy from the APIServer resource.
160-
// This will be used to determine if the components should honor the cluster-wide TLS profile.
161-
tlsAdherencePolicy, err := utiltls.FetchAPIServerTLSAdherencePolicy(context.Background(), k8sClient)
162-
if err != nil {
163-
klog.Fatalf("unable to get TLS adherence policy from API server: %v", err)
164-
}
165-
166-
// Fetch the TLS profile from the APIServer resource.
167-
tlsSecurityProfileSpec, err := utiltls.FetchAPIServerTLSProfile(context.Background(), k8sClient)
157+
// Resolve the TLS configuration for the server endpoints.
158+
tlsResult, err := resolveTLSConfig(context.Background(), workloadConfig, tlsMinVersionFlag, tlsCipherSuitesFlag)
168159
if err != nil {
169-
klog.Fatalf("unable to get TLS profile from API server: %v", err)
170-
}
171-
172-
// Create a default TLS configuration function.
173-
tlsConfig := func(*tls.Config) {}
174-
175-
if libgocrypto.ShouldHonorClusterTLSProfile(tlsAdherencePolicy) {
176-
var unsupportedCiphers []string
177-
// The TLS adherence policy indicates that the components should honor the cluster-wide TLS profile.
178-
// Set the TLS configuration function to use the cluster-wide TLS profile.
179-
tlsConfig, unsupportedCiphers = utiltls.NewTLSConfigFromProfile(tlsSecurityProfileSpec)
180-
if len(unsupportedCiphers) > 0 {
181-
klog.Infof("TLS configuration contains unsupported ciphers that will be ignored: %v", unsupportedCiphers)
182-
}
160+
klog.Fatalf("unable to configure TLS: %v", err)
183161
}
184162

185163
// Create a context that can be cancelled when there is a need to shut down the manager .
@@ -194,7 +172,7 @@ func main() {
194172
BindAddress: metricsBindAddress,
195173
SecureServing: true,
196174
FilterProvider: filters.WithAuthenticationAndAuthorization,
197-
TLSOpts: []func(*tls.Config){tlsConfig},
175+
TLSOpts: []func(*tls.Config){tlsResult.TLSConfig},
198176
},
199177
LeaderElectionNamespace: leaderElectResourceNamespace,
200178
LeaderElection: leaderElect,
@@ -283,26 +261,32 @@ func main() {
283261

284262
// Set up the TLS security profile watcher controller.
285263
// This will trigger a graceful shutdown when the TLS profile changes.
286-
if err := (&utiltls.SecurityProfileWatcher{
287-
Client: mgr.GetClient(),
288-
InitialTLSAdherencePolicy: tlsAdherencePolicy,
289-
InitialTLSProfileSpec: tlsSecurityProfileSpec,
290-
OnAdherencePolicyChange: func(ctx context.Context, oldTLSAdherencePolicy, newTLSAdherencePolicy configv1.TLSAdherencePolicy) {
291-
klog.Infof("TLS adherence policy has changed, initiating a shutdown to reload it. %q: %+v, %q: %+v",
292-
"old adherence policy", oldTLSAdherencePolicy,
293-
"new adherence policy", newTLSAdherencePolicy,
294-
)
295-
cancel()
296-
},
297-
OnProfileChange: func(ctx context.Context, oldTLSProfileSpec, newTLSProfileSpec configv1.TLSProfileSpec) {
298-
klog.Infof("TLS profile has changed, initiating a shutdown to reload it. %q: %+v, %q: %+v",
299-
"old profile", oldTLSProfileSpec,
300-
"new profile", newTLSProfileSpec,
301-
)
302-
cancel()
303-
},
304-
}).SetupWithManager(mgr); err != nil {
305-
klog.Fatalf("unable to create TLS security profile watcher controller: %v", err)
264+
// When TLS is overridden via CLI flags, the watcher is not needed since
265+
// the component is not reading from apiservers.config.openshift.io/cluster.
266+
if tlsOverrideFromFlags {
267+
klog.Info("TLS security profile watcher disabled because TLS is configured via CLI flags")
268+
} else {
269+
if err := (&utiltls.SecurityProfileWatcher{
270+
Client: mgr.GetClient(),
271+
InitialTLSAdherencePolicy: tlsResult.TLSAdherencePolicy,
272+
InitialTLSProfileSpec: tlsResult.TLSProfileSpec,
273+
OnAdherencePolicyChange: func(ctx context.Context, oldTLSAdherencePolicy, newTLSAdherencePolicy configv1.TLSAdherencePolicy) {
274+
klog.Infof("TLS adherence policy has changed, initiating a shutdown to reload it. %q: %+v, %q: %+v",
275+
"old adherence policy", oldTLSAdherencePolicy,
276+
"new adherence policy", newTLSAdherencePolicy,
277+
)
278+
cancel()
279+
},
280+
OnProfileChange: func(ctx context.Context, oldTLSProfileSpec, newTLSProfileSpec configv1.TLSProfileSpec) {
281+
klog.Infof("TLS profile has changed, initiating a shutdown to reload it. %q: %+v, %q: %+v",
282+
"old profile", oldTLSProfileSpec,
283+
"new profile", newTLSProfileSpec,
284+
)
285+
cancel()
286+
},
287+
}).SetupWithManager(mgr); err != nil {
288+
klog.Fatalf("unable to create TLS security profile watcher controller: %v", err)
289+
}
306290
}
307291

308292
// Start the Cmd

tls.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
Copyright 2017 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"crypto/tls"
22+
"fmt"
23+
24+
configv1 "github.com/openshift/api/config/v1"
25+
utiltls "github.com/openshift/controller-runtime-common/pkg/tls"
26+
libgocrypto "github.com/openshift/library-go/pkg/crypto"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/client-go/rest"
29+
cliflag "k8s.io/component-base/cli/flag"
30+
"k8s.io/klog/v2"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
)
33+
34+
// tlsConfigResult holds the resolved TLS configuration along with the
35+
// cluster-wide TLS profile metadata needed by the SecurityProfileWatcher.
36+
type tlsConfigResult struct {
37+
// TLSConfig is a function that applies TLS settings to a tls.Config.
38+
TLSConfig func(*tls.Config)
39+
// TLSAdherencePolicy is the cluster-wide TLS adherence policy.
40+
// Only populated when CLI flags are not set.
41+
TLSAdherencePolicy configv1.TLSAdherencePolicy
42+
// TLSProfileSpec is the cluster-wide TLS profile spec.
43+
// Only populated when CLI flags are not set.
44+
TLSProfileSpec configv1.TLSProfileSpec
45+
}
46+
47+
// resolveTLSConfig builds the TLS configuration. When CLI flags are set, they
48+
// take precedence over the cluster-wide TLS profile. When not set, the profile
49+
// from apiservers.config.openshift.io/cluster is fetched and applied if the
50+
// adherence policy requires it.
51+
func resolveTLSConfig(ctx context.Context, restConfig *rest.Config, tlsMinVersion string, tlsCipherSuites []string) (tlsConfigResult, error) {
52+
// If CLI flags are set they take precedence over the cluster-wide TLS profile.
53+
if tlsMinVersion != "" || len(tlsCipherSuites) > 0 {
54+
klog.Info("TLS configuration overridden via CLI flags, skipping honoring the cluster-wide TLS profile")
55+
56+
minVersion, err := cliflag.TLSVersion(tlsMinVersion)
57+
if err != nil {
58+
return tlsConfigResult{}, fmt.Errorf("invalid --tls-min-version value: %w", err)
59+
}
60+
cipherSuites, err := cliflag.TLSCipherSuites(tlsCipherSuites)
61+
if err != nil {
62+
return tlsConfigResult{}, fmt.Errorf("invalid --tls-cipher-suites value: %w", err)
63+
}
64+
65+
return tlsConfigResult{
66+
TLSConfig: func(cfg *tls.Config) {
67+
cfg.MinVersion = minVersion
68+
// Only set CipherSuites when MinVersion is below TLS 1.3, as Go's TLS 1.3 implementation
69+
// does not allow configuring cipher suites - all TLS 1.3 ciphers are always enabled.
70+
// See: https://github.com/golang/go/issues/29349
71+
if minVersion != tls.VersionTLS13 {
72+
cfg.CipherSuites = cipherSuites
73+
} else {
74+
klog.Warning("TLS 1.3 cipher suites are not configurable in Go, ignoring --tls-cipher-suites value")
75+
}
76+
},
77+
}, nil
78+
}
79+
80+
scheme := runtime.NewScheme()
81+
if err := configv1.AddToScheme(scheme); err != nil {
82+
return tlsConfigResult{}, fmt.Errorf("unable to add configv1 to scheme: %w", err)
83+
}
84+
85+
k8sClient, err := client.New(restConfig, client.Options{Scheme: scheme})
86+
if err != nil {
87+
return tlsConfigResult{}, fmt.Errorf("unable to create Kubernetes client: %w", err)
88+
}
89+
90+
tlsAdherencePolicy, err := utiltls.FetchAPIServerTLSAdherencePolicy(ctx, k8sClient)
91+
if err != nil {
92+
klog.Errorf("unable to get TLS adherence policy from API server: %v", err)
93+
// Default to empty string if the API server is not available or the field is not set.
94+
// This is the same behavior as the default value for the field when the field is omitted.
95+
// We will still keep a watch on the API server for the field and trigger a restart if the value changes.
96+
tlsAdherencePolicy = ""
97+
}
98+
99+
tlsProfileSpec, err := utiltls.FetchAPIServerTLSProfile(ctx, k8sClient)
100+
if err != nil {
101+
klog.Errorf("unable to get TLS profile from API server: %v", err)
102+
// Default to an empty profile if the API server is not available or the field is not set.
103+
// We will still keep a watch on the API server for the field and trigger a restart if the value changes.
104+
tlsProfileSpec = configv1.TLSProfileSpec{}
105+
}
106+
107+
// Default the TLS configuration to Go's crypto package default values.
108+
tlsConfig := func(*tls.Config) {}
109+
110+
// If the cluster-wide TLS adherence policy is set to honor the cluster-wide TLS profile,
111+
// use the cluster-wide TLS profile-based configuration.
112+
if libgocrypto.ShouldHonorClusterTLSProfile(tlsAdherencePolicy) {
113+
profileTLSConfig, unsupportedCiphers := utiltls.NewTLSConfigFromProfile(tlsProfileSpec)
114+
if len(unsupportedCiphers) > 0 {
115+
klog.Infof("TLS configuration contains unsupported ciphers that will be ignored: %v", unsupportedCiphers)
116+
}
117+
118+
// Set the TLS configuration to the cluster-wide TLS profile-based configuration.
119+
tlsConfig = profileTLSConfig
120+
}
121+
122+
return tlsConfigResult{
123+
TLSConfig: tlsConfig,
124+
TLSAdherencePolicy: tlsAdherencePolicy,
125+
TLSProfileSpec: tlsProfileSpec,
126+
}, nil
127+
}

0 commit comments

Comments
 (0)