Skip to content

Commit f852bdf

Browse files
committed
ACM-30179: Use TLS configuration from the APIServer when available
OCP 4.22 indroduces a new feature in the APIServer for configuring TLS configuration, and we want our components to also use the same TLS configuration that is defined in the APIServer. When starting a components we will attempt to get the APIServer and check its TLSAdherencePolicy (decides whether we should apply the TLS configuration) and TLSProfileSpec (the actual TLS configuration). For our operator we also add a controller called SecurityProfileWatcher (provided by openshift/controller-runtime-common) in order to force a restart when these values change. For our server we implement a similar behavior ourselves.
1 parent a3b4561 commit f852bdf

662 files changed

Lines changed: 148824 additions & 12 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
annotations:
5+
service.beta.openshift.io/serving-cert-secret-name: ibi-metrics-serving-certs
6+
creationTimestamp: null
7+
name: image-based-install-metrics
8+
spec:
9+
ports:
10+
- port: 8080
11+
protocol: TCP
12+
targetPort: 8080
13+
selector:
14+
app: image-based-install-operator
15+
status:
16+
loadBalancer: {}

bundle/manifests/image-based-install-operator.clusterserviceversion.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ metadata:
3636
}
3737
]
3838
capabilities: Basic Install
39-
createdAt: "2026-04-23T10:32:42Z"
39+
createdAt: "2026-04-23T10:49:15Z"
4040
operators.operatorframework.io/builder: operator-sdk-v1.30.0
4141
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
4242
name: image-based-install-operator.v0.0.1
@@ -82,6 +82,26 @@ spec:
8282
- patch
8383
- update
8484
- watch
85+
- apiGroups:
86+
- authentication.k8s.io
87+
resources:
88+
- tokenreviews
89+
verbs:
90+
- create
91+
- apiGroups:
92+
- authorization.k8s.io
93+
resources:
94+
- subjectaccessreviews
95+
verbs:
96+
- create
97+
- apiGroups:
98+
- config.openshift.io
99+
resources:
100+
- apiservers
101+
verbs:
102+
- get
103+
- list
104+
- watch
85105
- apiGroups:
86106
- extensions.hive.openshift.io
87107
resources:
@@ -228,6 +248,8 @@ spec:
228248
name: data
229249
- mountPath: /webhook-certs
230250
name: webhook-certs
251+
- mountPath: /metrics-certs
252+
name: metrics-certs
231253
- command:
232254
- /usr/local/bin/server
233255
env:
@@ -281,6 +303,9 @@ spec:
281303
- name: webhook-certs
282304
secret:
283305
secretName: ibi-webhook-serving-certs
306+
- name: metrics-certs
307+
secret:
308+
secretName: ibi-metrics-serving-certs
284309
permissions:
285310
- rules:
286311
- apiGroups:

cmd/manager/main.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package main
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"flag"
2223
"fmt"
2324
"net/http"
@@ -26,11 +27,13 @@ import (
2627
"os"
2728
"time"
2829

30+
crtls "github.com/openshift/controller-runtime-common/pkg/tls"
2931
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
3032
// to ensure that exec-entrypoint and run can make use of them.
3133
_ "k8s.io/client-go/plugin/pkg/client/auth"
3234
"sigs.k8s.io/controller-runtime/pkg/manager"
3335

36+
configv1 "github.com/openshift/api/config/v1"
3437
corev1 "k8s.io/api/core/v1"
3538
"k8s.io/apimachinery/pkg/labels"
3639
"k8s.io/apimachinery/pkg/runtime"
@@ -41,6 +44,7 @@ import (
4144
"sigs.k8s.io/controller-runtime/pkg/client"
4245
"sigs.k8s.io/controller-runtime/pkg/healthz"
4346
"sigs.k8s.io/controller-runtime/pkg/log/zap"
47+
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
4448
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
4549
"sigs.k8s.io/controller-runtime/pkg/webhook"
4650

@@ -54,6 +58,7 @@ import (
5458
"github.com/openshift/image-based-install-operator/internal/credentials"
5559
"github.com/openshift/image-based-install-operator/internal/installer"
5660
"github.com/openshift/image-based-install-operator/internal/monitor"
61+
"github.com/openshift/image-based-install-operator/internal/tlsconfig"
5762
//+kubebuilder:scaffold:imports
5863
)
5964

@@ -67,6 +72,7 @@ func init() {
6772
utilruntime.Must(v1alpha1.AddToScheme(scheme))
6873
utilruntime.Must(bmh_v1alpha1.AddToScheme(scheme))
6974
utilruntime.Must(hivev1.AddToScheme(scheme))
75+
utilruntime.Must(configv1.AddToScheme(scheme))
7076
//+kubebuilder:scaffold:scheme
7177
}
7278

@@ -96,17 +102,32 @@ func main() {
96102
go startPPROF(logger)
97103
}
98104

99-
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
105+
ctx, cancel := context.WithCancel(ctrl.SetupSignalHandler())
106+
defer cancel()
107+
108+
restCfg := ctrl.GetConfigOrDie()
109+
tlsResult, err := tlsconfig.ResolveTLSConfig(context.Background(), restCfg)
110+
if err != nil {
111+
setupLog.Error(err, "unable to resolve TLS config")
112+
os.Exit(1)
113+
}
114+
115+
mgr, err := ctrl.NewManager(restCfg, ctrl.Options{
100116
Scheme: scheme,
101117
Metrics: metricsserver.Options{
102-
BindAddress: metricsAddr,
118+
BindAddress: metricsAddr,
119+
SecureServing: true,
120+
CertDir: "/metrics-certs",
121+
FilterProvider: filters.WithAuthenticationAndAuthorization,
122+
TLSOpts: []func(*tls.Config){tlsResult.TLSConfig},
103123
},
104124
HealthProbeBindAddress: probeAddr,
105125
LeaderElection: enableLeaderElection,
106126
LeaderElectionID: "e21b2704.openshift.io",
107127
WebhookServer: webhook.NewServer(webhook.Options{
108128
Port: 9443,
109129
CertDir: "/webhook-certs",
130+
TLSOpts: []func(*tls.Config){tlsResult.TLSConfig},
110131
}),
111132
Cache: cache.Options{
112133
ByObject: map[client.Object]cache.ByObject{
@@ -179,6 +200,25 @@ func main() {
179200
os.Exit(1)
180201
}
181202

203+
if err := (&crtls.SecurityProfileWatcher{
204+
Client: mgr.GetClient(),
205+
InitialTLSAdherencePolicy: tlsResult.TLSAdherencePolicy,
206+
InitialTLSProfileSpec: tlsResult.TLSProfileSpec,
207+
OnAdherencePolicyChange: func(_ context.Context, oldPolicy, newPolicy configv1.TLSAdherencePolicy) {
208+
logger.Infof("TLS adherence policy has changed, shutting down to reload, oldPolicy: %v, newPolicy: %v",
209+
oldPolicy, newPolicy)
210+
cancel()
211+
},
212+
OnProfileChange: func(_ context.Context, oldProfile, newProfile configv1.TLSProfileSpec) {
213+
logger.Infof("TLS profile has changed, shutting down to reload, oldProfile: %v, newProfile: %v",
214+
oldProfile, newProfile)
215+
cancel()
216+
},
217+
}).SetupWithManager(mgr); err != nil {
218+
setupLog.Error(err, "unable to create TLS security profile watcher")
219+
os.Exit(1)
220+
}
221+
182222
if err = (&v1alpha1.ImageClusterInstall{}).SetupWebhookWithManager(mgr); err != nil {
183223
setupLog.Error(err, "unable to create webhook", "webhook", "ImageClusterInstall")
184224
os.Exit(1)
@@ -197,7 +237,7 @@ func main() {
197237
go EnqueueExistingImageClusterInstall(mgr)
198238

199239
setupLog.Info("starting manager")
200-
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
240+
if err := mgr.Start(ctx); err != nil {
201241
setupLog.Error(err, "problem running manager")
202242
os.Exit(1)
203243
}

cmd/server/main.go

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package main
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"errors"
7+
"flag"
58
"fmt"
69
"net/http"
710
"os"
@@ -11,8 +14,21 @@ import (
1114
"time"
1215

1316
"github.com/kelseyhightower/envconfig"
14-
"github.com/openshift/image-based-install-operator/internal/imageserver"
17+
configv1 "github.com/openshift/api/config/v1"
18+
configclientset "github.com/openshift/client-go/config/clientset/versioned"
19+
crtls "github.com/openshift/controller-runtime-common/pkg/tls"
1520
"github.com/sirupsen/logrus"
21+
"k8s.io/apimachinery/pkg/api/equality"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/watch"
24+
25+
"github.com/openshift/image-based-install-operator/internal/imageserver"
26+
"github.com/openshift/image-based-install-operator/internal/tlsconfig"
27+
28+
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
29+
_ "k8s.io/client-go/plugin/pkg/client/auth"
30+
ctrl "sigs.k8s.io/controller-runtime"
31+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
1632
)
1733

1834
var Options struct {
@@ -23,6 +39,13 @@ var Options struct {
2339
}
2440

2541
func main() {
42+
opts := zap.Options{
43+
Development: true,
44+
}
45+
opts.BindFlags(flag.CommandLine)
46+
flag.Parse()
47+
48+
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
2649
log := logrus.New()
2750
log.SetReportCaller(true)
2851

@@ -47,10 +70,27 @@ func main() {
4770
ReadHeaderTimeout: 5 * time.Second,
4871
}
4972

73+
watchCtx, watchCancel := context.WithCancel(context.Background())
74+
defer watchCancel()
75+
tlsProfileChanged := make(chan struct{}, 1)
76+
5077
go func() {
5178
var err error
5279
if Options.HTTPSKeyFile != "" && Options.HTTPSCertFile != "" {
80+
var tlsResult tlsconfig.TLSConfigResult
5381
log.Infof("Starting https handler on %s...", server.Addr)
82+
83+
restCfg := ctrl.GetConfigOrDie()
84+
tlsResult, err = tlsconfig.ResolveTLSConfig(context.Background(), restCfg)
85+
if err != nil {
86+
log.WithError(err).Fatal("unable to configure HTTPS TLS")
87+
}
88+
go watchAndExitOnTLSChange(watchCtx, log, configclientset.NewForConfigOrDie(restCfg), tlsResult, tlsProfileChanged)
89+
90+
if tlsResult.TLSConfig != nil {
91+
server.TLSConfig = &tls.Config{}
92+
tlsResult.TLSConfig(server.TLSConfig)
93+
}
5494
err = server.ListenAndServeTLS(Options.HTTPSCertFile, Options.HTTPSKeyFile)
5595
} else {
5696
log.Infof("Starting http handler on %s...", server.Addr)
@@ -62,11 +102,20 @@ func main() {
62102
}
63103
}()
64104

65-
stop := make(chan os.Signal, 1)
66-
signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
67-
<-stop
105+
sigCh := make(chan os.Signal, 1)
106+
signal.Notify(sigCh, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
107+
defer signal.Stop(sigCh)
108+
109+
select {
110+
case <-sigCh:
111+
case <-tlsProfileChanged:
112+
log.Info("initiating graceful shutdown after cluster TLS configuration change")
113+
}
114+
watchCancel()
68115

69-
if err := server.Shutdown(context.Background()); err != nil {
116+
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
117+
defer shutdownCancel()
118+
if err := server.Shutdown(shutdownCtx); err != nil {
70119
log.WithError(err).Error("shutdown failed")
71120
if err := server.Close(); err != nil {
72121
log.WithError(err).Fatal("emergency shutdown failed")
@@ -75,3 +124,56 @@ func main() {
75124
log.Info("server terminated gracefully")
76125
}
77126
}
127+
128+
func watchAndExitOnTLSChange(ctx context.Context, log *logrus.Logger, configClient configclientset.Interface, current tlsconfig.TLSConfigResult, requestShutdown chan<- struct{}) {
129+
w, err := configClient.ConfigV1().APIServers().Watch(ctx, metav1.ListOptions{
130+
FieldSelector: "metadata.name=cluster",
131+
})
132+
if err != nil {
133+
log.WithError(err).Error("failed to watch the APIServer, TLS updates will not be monitored")
134+
return
135+
}
136+
defer w.Stop()
137+
138+
for event := range w.ResultChan() {
139+
if event.Type != watch.Modified {
140+
continue
141+
}
142+
updated, ok := event.Object.(*configv1.APIServer)
143+
if !ok {
144+
continue
145+
}
146+
147+
if current.TLSAdherencePolicy != updated.Spec.TLSAdherence {
148+
log.Infof("TLS adherence policy has changed, shutting down to reload, oldPolicy: %v, newPolicy: %v",
149+
current.TLSAdherencePolicy, updated.Spec.TLSAdherence)
150+
requestGracefulShutdown(requestShutdown)
151+
return
152+
}
153+
154+
profile, err := crtls.GetTLSProfileSpec(updated.Spec.TLSSecurityProfile)
155+
if err != nil {
156+
log.WithError(err).Error("failed to load TLS profile spec after APIServer update, ignoring")
157+
continue
158+
}
159+
if !equality.Semantic.DeepEqual(current.TLSProfileSpec, profile) {
160+
log.Infof("TLS profile has changed, shutting down to reload, oldProfile: %v, newProfile: %v",
161+
current.TLSProfileSpec, profile)
162+
requestGracefulShutdown(requestShutdown)
163+
return
164+
}
165+
}
166+
167+
if errors.Is(ctx.Err(), context.Canceled) {
168+
log.Info("stopped monitoring APIServer TLS updates")
169+
return
170+
}
171+
log.Error("watch on APIServer exited, TLS updates will not be monitored")
172+
}
173+
174+
func requestGracefulShutdown(ch chan<- struct{}) {
175+
select {
176+
case ch <- struct{}{}:
177+
default:
178+
}
179+
}

config/manager/manager.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ spec:
7979
mountPath: /data
8080
- name: webhook-certs
8181
mountPath: /webhook-certs
82+
- name: metrics-certs
83+
mountPath: /metrics-certs
8284
- command:
8385
- /usr/local/bin/server
8486
image: controller:latest
@@ -126,6 +128,9 @@ spec:
126128
- name: webhook-certs
127129
secret:
128130
secretName: ibi-webhook-serving-certs
131+
- name: metrics-certs
132+
secret:
133+
secretName: ibi-metrics-serving-certs
129134
serviceAccountName: image-based-install-operator
130135
terminationGracePeriodSeconds: 10
131136
---
@@ -143,3 +148,18 @@ spec:
143148
name: config-server
144149
selector:
145150
app: image-based-install-operator
151+
---
152+
apiVersion: v1
153+
kind: Service
154+
metadata:
155+
name: image-based-install-metrics
156+
namespace: image-based-install-operator
157+
annotations:
158+
service.beta.openshift.io/serving-cert-secret-name: ibi-metrics-serving-certs
159+
spec:
160+
ports:
161+
- port: 8080
162+
protocol: TCP
163+
targetPort: 8080
164+
selector:
165+
app: image-based-install-operator

0 commit comments

Comments
 (0)